Repository files navigation

Refit: The automatic type-safe REST library for .NET Core, Xamarin and .NET

Refit is a library heavily inspired by Square's Retrofit library, and it turns your REST API into a live interface:

publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);}

The RestService class generates an implementation of IGitHubApi that uses HttpClient to make its calls:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com");varoctocat=awaitgitHubApi.GetUser("octocat");

Where does this work?

Refit currently supports the following platforms and any .NET Standard 1.4 target:

  • UWP
  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS
  • Desktop .NET 4.6.1
  • .NET Core

Note about .NET Core

For .NET Core build-time support, you must use the .NET Core 2 SDK. You can target any supported platform in your library, long as the 2.0+ SDK is used at build-time.

API Attributes

Every method must have an HTTP attribute that provides the request method and relative URL. There are six built-in annotations: Get, Post, Put, Delete, Patch and Head. The relative URL of the resource is specified in the annotation.

[Get("/users/list")]

You can also specify query parameters in the URL:

[Get("/users/list?sort=desc")]

A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }.

If the name of your parameter doesn't match the name in the URL path, use the AliasAs attribute.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId);

Parameters that are not specified as a URL substitution will automatically be used as query parameters. This is different than Retrofit, where all parameters must be explicitly specified.

The comparison between parameter name and URL parameter is not case-sensitive, so it will work correctly if you name your parameter groupId in the path /group/{groupid}/show for example.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,[AliasAs("sort")]stringsortOrder);GroupList(4,"desc");>>>"/group/4/users?sort=desc"

Dynamic Querystring Parameters

If you specify an object as a query parameter, all public properties which are not null are used as query parameters. Use the Query attribute the change the behavior to 'flatten' your query parameter object. If using this Attribute you can specify values for the Delimiter and the Prefix which are used to 'flatten' the object.

publicclassMyQueryParams{[AliasAs("order")]publicstringSortOrder{get;set;}publicintLimit{get;set;}}[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,MyQueryParamsparams);[Get("/group/{id}/users")]Task<List<User>>GroupListWithAttribute([AliasAs("id")]intgroupId,[Query(".","search")]MyQueryParamsparams);params.SortOrder="desc";params.Limit=10;GroupList(4,params)>>>"/group/4/users?order=desc&Limit=10"
GroupListWithAttribute(4,params)>>>"/group/4/users?search.order=desc&search.Limit=10"

A similar behavior exists if using a Dictionary, but without the advantages of the AliasAs attributes and of course no intellisense and/or type safety.

Collections as Querystring parameters

Use the Query attribute to specify format in which collections should be formatted in query string

[Get("/users/list")]TaskSearch([Query(CollectionFormat.Multi)]int[]ages);Search(new[]{10,20,30})>>>"/users/list?ages=10&ages=20&ages=30"[Get("/users/list")]
Task Search([Query(CollectionFormat.Csv)]int[]ages);
Search(new[]{10,20,30})>>>"/users/list?ages=10%2C20%2C30"

Body content

One of the parameters in your method can be used as the body, by using the Body attribute:

[Post("/users/new")]TaskCreateUser([Body]Useruser);

There are four possibilities for supplying the body data, depending on the type of the parameter:

  • If the type is Stream, the content will be streamed via StreamContent
  • If the type is string, the string will be used directly as the content
  • If the parameter has the attribute [Body(BodySerializationMethod.UrlEncoded)], the content will be URL-encoded (see form posts below)
  • For all other types, the object will be serialized as JSON.

Buffering and the Content-Length header

By default, Refit streams the body content without buffering it. This means you can stream a file from disk, for example, without incurring the overhead of loading the whole file into memory. The downside of this is that no Content-Length header is set on the request. If your API needs you to send a Content-Length header with the request, you can disable this streaming behavior by setting the buffered argument of the [Body] attribute to true:

TaskCreateUser([Body(buffered:true)]Useruser);

JSON content

JSON requests and responses are serialized/deserialized using Json.NET. By default, Refit will use the serializer settings that can be configured by setting Newtonsoft.Json.JsonConvert.DefaultSettings:

JsonConvert.DefaultSettings=()=>newJsonSerializerSettings(){ContractResolver=newCamelCasePropertyNamesContractResolver(),Converters={newStringEnumConverter()}};// Serialized as: {"day":"Saturday"}awaitPostSomeStuff(new{Day=DayOfWeek.Saturday});

As these are global settings they will affect your entire application. It might be beneficial to isolate the settings for calls to a particular API. When creating a Refit generated live interface, you may optionally pass a RefitSettings that will allow you to specify what serializer settings you would like. This allows you to have different serializer settings for separate APIs:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newSnakeCasePropertyNamesContractResolver()}});varotherApi=RestService.For<IOtherApi>("https://api.example.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newCamelCasePropertyNamesContractResolver()}});

Property serialization/deserialization can be customised using Json.NET's JsonProperty attribute:

publicclassFoo{// Works like [AliasAs("b")] would in form posts (see below)[JsonProperty(PropertyName="b")]publicstringBar{get;set;}}

Form posts

For APIs that take form posts (i.e. serialized as application/x-www-form-urlencoded), initialize the Body attribute with BodySerializationMethod.UrlEncoded.

The parameter can be an IDictionary:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Dictionary<string,object>data);}vardata=newDictionary<string,object>{{"v",1},{"tid","UA-1234-5"},{"cid",newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c")},{"t","event"},};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(data);

Or you can just pass any object and all public, readable properties will be serialized as form fields in the request. This approach allows you to alias property names using [AliasAs("whatever")] which can help if the API has cryptic field names:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Measurementmeasurement);}publicclassMeasurement{// Properties can be read-only and [AliasAs] isn't requiredpublicintv{get{return1;}}[AliasAs("tid")]publicstringWebPropertyId{get;set;}[AliasAs("cid")]publicGuidClientId{get;set;}[AliasAs("t")]publicstringType{get;set;}publicobjectIgnoreMe{privateget;set;}}varmeasurement=newMeasurement{WebPropertyId="UA-1234-5",ClientId=newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c"),Type="event"};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(measurement);

If you have a type that has [JsonProperty(PropertyName)] attributes setting property aliases, Refit will use those too ([AliasAs] will take precedence where you have both). This means that the following type will serialize as one=value1&two=value2:

publicclassSomeObject{[JsonProperty(PropertyName="one")]publicstringFirstProperty{get;set;}[JsonProperty(PropertyName="notTwo")][AliasAs("two")]publicstringSecondProperty{get;set;}}

NOTE: This use of AliasAs applies to querystring parameters and form body posts, but not to response objects; for aliasing fields on response objects, you'll still need to use [JsonProperty("full-property-name")].

Setting request headers

Static headers

You can set one or more static request headers for a request applying a Headers attribute to the method:

[Headers("User-Agent: Awesome Octocat App")][Get("/users/{user}")]Task<User>GetUser(stringuser);

Static headers can also be added to every request in the API by applying the Headers attribute to the interface:

[Headers("User-Agent: Awesome Octocat App")]publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser);}

Dynamic headers

If the content of the header needs to be set at runtime, you can add a header with a dynamic value to a request by applying a Header attribute to a parameter:

[Get("/users/{user}")]Task<User>GetUser(stringuser,[Header("Authorization")]stringauthorization);// Will add the header "Authorization: token OAUTH-TOKEN" to the requestvaruser=awaitGetUser("octocat","token OAUTH-TOKEN");

Authorization (Dynamic Headers redux)

The most common reason to use headers is for authorization. Today most API's use some flavor of oAuth with access tokens that expire and refresh tokens that are longer lived.

One way to encapsulate these kinds of token usage, a custom HttpClientHandler can be inserted instead.

For example:

classAuthenticatedHttpClientHandler:HttpClientHandler{privatereadonlyFunc<Task<string>>getToken;publicAuthenticatedHttpClientHandler(Func<Task<string>>getToken){if(getToken==null)thrownewArgumentNullException(nameof(getToken));this.getToken=getToken;}protectedoverrideasyncTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest,CancellationTokencancellationToken){// See if the request has an authorize headervarauth=request.Headers.Authorization;if(auth!=null){vartoken=awaitgetToken().ConfigureAwait(false);request.Headers.Authorization=newAuthenticationHeaderValue(auth.Scheme,token);}returnawaitbase.SendAsync(request,cancellationToken).ConfigureAwait(false);}}

While HttpClient contains a nearly identical method signature, it is used differently. HttpClient.SendAsync is not called by Refit. The HttpClientHandler must be modified instead.

This class is used like so (example uses the ADAL library to manage auto-token refresh but the principal holds for Xamarin.Auth or any other library:

classLoginViewModel{AuthenticationContextcontext=newAuthenticationContext(...);privateasyncTask<string>GetToken(){// The AcquireTokenAsync call will prompt with a UI if necessary// Or otherwise silently use a refresh token to return// a valid access token	vartoken=awaitcontext.AcquireTokenAsync("http://my.service.uri/app","clientId",newUri("callback://complete"));returntoken;}publicasyncvoidLoginAndCallApi(){varapi=RestService.For<IMyRestService>(newHttpClient(newAuthenticatedHttpClientHandler(GetToken)){BaseAddress=newUri("https://the.end.point/")});varlocation=awaitapi.GetLocationOfRebelBase();}}interfaceIMyRestService{[Get("/getPublicInfo")]Task<Foobar>SomePublicMethod();[Get("/secretStuff")][Headers("Authorization: Bearer")]Task<Location>GetLocationOfRebelBase();}

In the above example, any time a method that requires authentication is called, the AuthenticatedHttpClientHandler will try to get a fresh access token. It's up to the app to provide one, checking the expiration time of an existing access token and obtaining a new one if needed.

Redefining headers

Unlike Retrofit, where headers do not overwrite each other and are all added to the request regardless of how many times the same header is defined, Refit takes a similar approach to the approach ASP.NET MVC takes with action filters — redefining a header will replace it, in the following order of precedence:

  • Headers attribute on the interface (lowest priority)
  • Headers attribute on the method
  • Header attribute on a method parameter (highest priority)
[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")]Task<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji: :smile_cat:")]Task<User>GetUser(stringuser);[Post("/users/new")][Headers("X-Emoji: :metal:")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// X-Emoji: :rocket:varusers=awaitGetUsers();// X-Emoji: :smile_cat:varuser=awaitGetUser("octocat");// X-Emoji: :trollface:awaitCreateUser(user,":trollface:");

Removing headers

Headers defined on an interface or method can be removed by redefining a static header without a value (i.e. without : <value>) or passing null for a dynamic header. Empty strings will be included as empty headers.

[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")][Headers("X-Emoji")]// Remove the X-Emoji headerTask<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji:")]// Redefine the X-Emoji header as emptyTask<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// No X-Emoji headervarusers=awaitGetUsers();// X-Emoji: varuser=awaitGetUser("octocat");// No X-Emoji headerawaitCreateUser(user,null);// X-Emoji: awaitCreateUser(user,"");

Multipart uploads

Methods decorated with Multipart attribute will be submitted with multipart content type. At this time, multipart methods support the following parameter types:

  • string (parameter name will be used as name and string value as value)
  • byte array
  • Stream
  • FileInfo

The parameter name will be used as the name of the field in the multipart data. This can be overridden with the AliasAs attribute.

To specify the file name and content type for byte array (byte[]), Stream and FileInfo parameters, use of a wrapper class is required. The wrapper classes for these types are ByteArrayPart, StreamPart and FileInfoPart.

publicinterfaceISomeApi{[Multipart][Post("/users/{id}/photo")]TaskUploadPhoto(intid,[AliasAs("myPhoto")]StreamPartstream);}

To pass a Stream to this method, construct a StreamPart object like so:

someApiInstance.UploadPhoto(id,newStreamPart(myPhotoStream,"photo.jpg","image/jpeg"));

Note: The AttachmentName attribute that was previously described in this section has been deprecated and its use is not recommended.

Retrieving the response

Note that in Refit unlike in Retrofit, there is no option for a synchronous network request - all requests must be async, either via Task or via IObservable. There is also no option to create an async method via a Callback parameter unlike Retrofit, because we live in the async/await future.

Similarly to how body content changes via the parameter type, the return type will determine the content returned.

Returning Task without a type parameter will discard the content and solely tell you whether or not the call succeeded:

[Post("/users/new")]TaskCreateUser([Body]Useruser);// This will throw if the network call failsawaitCreateUser(someUser);

If the type parameter is 'HttpResponseMessage' or 'string', the raw response message or the content as a string will be returned respectively.

// Returns the content as a string (i.e. the JSON data)[Get("/users/{user}")]Task<string>GetUser(stringuser);// Returns the raw response, as an IObservable that can be used with the// Reactive Extensions[Get("/users/{user}")]IObservable<HttpResponseMessage>GetUser(stringuser);

Using generic interfaces

When using something like ASP.NET Web API, it's a fairly common pattern to have a whole stack of CRUD REST services. Refit now supports these, allowing you to define a single API interface with a generic type:

publicinterfaceIReallyExcitingCrudApi<T,inTKey>whereT:class{[Post("")]Task<T>Create([Body]Tpayload);[Get("")]Task<List<T>>ReadAll();[Get("/{key}")]Task<T>ReadOne(TKeykey);[Put("/{key}")]TaskUpdate(TKeykey,[Body]Tpayload);[Delete("/{key}")]TaskDelete(TKeykey);}

Which can be used like this:

// The "/users" part here is kind of important if you want it to work for more // than one type (unless you have a different domain for each type)varapi=RestService.For<IReallyExcitingCrudApi<User,string>>("http://api.example.com/users");

Using HttpClientFactory

Refit has first class support for the ASP.Net Core 2.1 HttpClientFactory. Add a reference to Refit.HttpClientFactory and call the provided extension method in your ConfigureServices method to configure your Refit interface:

services.AddRefitClient<IWebApi>().ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Optionally, a RefitSettings object can be included:

varsettings=newRefitSettings();// Configure refit settings hereservices.AddRefitClient<IWebApi>(settings).ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Note that some of the properties of RefitSettings will be ignored because the HttpClient and HttpClientHandlers will be managed by the HttpClientFactory instead of Refit.

You can then get the api interface using constructor injection:

publicclassHomeController:Controller{publicHomeController(IWebApiwebApi){_webApi=webApi;}privatereadonlyIWebApi_webApi;publicasyncTask<IActionResult>Index(CancellationTokencancellationToken){varthing=await_webApi.GetSomethingWeNeed(cancellationToken);returnView(thing);}}

About

The automatic type-safe REST library for Xamarin and .NET

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Refit: The automatic type-safe REST library for .NET Core, Xamarin and .NET

Refit is a library heavily inspired by Square's Retrofit library, and it turns your REST API into a live interface:

publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);}

The RestService class generates an implementation of IGitHubApi that uses HttpClient to make its calls:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com");varoctocat=awaitgitHubApi.GetUser("octocat");

Where does this work?

Refit currently supports the following platforms and any .NET Standard 1.4 target:

  • UWP
  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS
  • Desktop .NET 4.6.1
  • .NET Core

Note about .NET Core

For .NET Core build-time support, you must use the .NET Core 2 SDK. You can target any supported platform in your library, long as the 2.0+ SDK is used at build-time.

API Attributes

Every method must have an HTTP attribute that provides the request method and relative URL. There are six built-in annotations: Get, Post, Put, Delete, Patch and Head. The relative URL of the resource is specified in the annotation.

[Get("/users/list")]

You can also specify query parameters in the URL:

[Get("/users/list?sort=desc")]

A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }.

If the name of your parameter doesn't match the name in the URL path, use the AliasAs attribute.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId);

Parameters that are not specified as a URL substitution will automatically be used as query parameters. This is different than Retrofit, where all parameters must be explicitly specified.

The comparison between parameter name and URL parameter is not case-sensitive, so it will work correctly if you name your parameter groupId in the path /group/{groupid}/show for example.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,[AliasAs("sort")]stringsortOrder);GroupList(4,"desc");>>>"/group/4/users?sort=desc"

Dynamic Querystring Parameters

If you specify an object as a query parameter, all public properties which are not null are used as query parameters. Use the Query attribute the change the behavior to 'flatten' your query parameter object. If using this Attribute you can specify values for the Delimiter and the Prefix which are used to 'flatten' the object.

publicclassMyQueryParams{[AliasAs("order")]publicstringSortOrder{get;set;}publicintLimit{get;set;}}[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,MyQueryParamsparams);[Get("/group/{id}/users")]Task<List<User>>GroupListWithAttribute([AliasAs("id")]intgroupId,[Query(".","search")]MyQueryParamsparams);params.SortOrder="desc";params.Limit=10;GroupList(4,params)>>>"/group/4/users?order=desc&Limit=10"
GroupListWithAttribute(4,params)>>>"/group/4/users?search.order=desc&search.Limit=10"

A similar behavior exists if using a Dictionary, but without the advantages of the AliasAs attributes and of course no intellisense and/or type safety.

Collections as Querystring parameters

Use the Query attribute to specify format in which collections should be formatted in query string

[Get("/users/list")]TaskSearch([Query(CollectionFormat.Multi)]int[]ages);Search(new[]{10,20,30})>>>"/users/list?ages=10&ages=20&ages=30"[Get("/users/list")]
Task Search([Query(CollectionFormat.Csv)]int[]ages);
Search(new[]{10,20,30})>>>"/users/list?ages=10%2C20%2C30"

Body content

One of the parameters in your method can be used as the body, by using the Body attribute:

[Post("/users/new")]TaskCreateUser([Body]Useruser);

There are four possibilities for supplying the body data, depending on the type of the parameter:

  • If the type is Stream, the content will be streamed via StreamContent
  • If the type is string, the string will be used directly as the content
  • If the parameter has the attribute [Body(BodySerializationMethod.UrlEncoded)], the content will be URL-encoded (see form posts below)
  • For all other types, the object will be serialized as JSON.

Buffering and the Content-Length header

By default, Refit streams the body content without buffering it. This means you can stream a file from disk, for example, without incurring the overhead of loading the whole file into memory. The downside of this is that no Content-Length header is set on the request. If your API needs you to send a Content-Length header with the request, you can disable this streaming behavior by setting the buffered argument of the [Body] attribute to true:

TaskCreateUser([Body(buffered:true)]Useruser);

JSON content

JSON requests and responses are serialized/deserialized using Json.NET. By default, Refit will use the serializer settings that can be configured by setting Newtonsoft.Json.JsonConvert.DefaultSettings:

JsonConvert.DefaultSettings=()=>newJsonSerializerSettings(){ContractResolver=newCamelCasePropertyNamesContractResolver(),Converters={newStringEnumConverter()}};// Serialized as: {"day":"Saturday"}awaitPostSomeStuff(new{Day=DayOfWeek.Saturday});

As these are global settings they will affect your entire application. It might be beneficial to isolate the settings for calls to a particular API. When creating a Refit generated live interface, you may optionally pass a RefitSettings that will allow you to specify what serializer settings you would like. This allows you to have different serializer settings for separate APIs:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newSnakeCasePropertyNamesContractResolver()}});varotherApi=RestService.For<IOtherApi>("https://api.example.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newCamelCasePropertyNamesContractResolver()}});

Property serialization/deserialization can be customised using Json.NET's JsonProperty attribute:

publicclassFoo{// Works like [AliasAs("b")] would in form posts (see below)[JsonProperty(PropertyName="b")]publicstringBar{get;set;}}

Form posts

For APIs that take form posts (i.e. serialized as application/x-www-form-urlencoded), initialize the Body attribute with BodySerializationMethod.UrlEncoded.

The parameter can be an IDictionary:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Dictionary<string,object>data);}vardata=newDictionary<string,object>{{"v",1},{"tid","UA-1234-5"},{"cid",newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c")},{"t","event"},};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(data);

Or you can just pass any object and all public, readable properties will be serialized as form fields in the request. This approach allows you to alias property names using [AliasAs("whatever")] which can help if the API has cryptic field names:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Measurementmeasurement);}publicclassMeasurement{// Properties can be read-only and [AliasAs] isn't requiredpublicintv{get{return1;}}[AliasAs("tid")]publicstringWebPropertyId{get;set;}[AliasAs("cid")]publicGuidClientId{get;set;}[AliasAs("t")]publicstringType{get;set;}publicobjectIgnoreMe{privateget;set;}}varmeasurement=newMeasurement{WebPropertyId="UA-1234-5",ClientId=newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c"),Type="event"};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(measurement);

If you have a type that has [JsonProperty(PropertyName)] attributes setting property aliases, Refit will use those too ([AliasAs] will take precedence where you have both). This means that the following type will serialize as one=value1&two=value2:

publicclassSomeObject{[JsonProperty(PropertyName="one")]publicstringFirstProperty{get;set;}[JsonProperty(PropertyName="notTwo")][AliasAs("two")]publicstringSecondProperty{get;set;}}

NOTE: This use of AliasAs applies to querystring parameters and form body posts, but not to response objects; for aliasing fields on response objects, you'll still need to use [JsonProperty("full-property-name")].

Setting request headers

Static headers

You can set one or more static request headers for a request applying a Headers attribute to the method:

[Headers("User-Agent: Awesome Octocat App")][Get("/users/{user}")]Task<User>GetUser(stringuser);

Static headers can also be added to every request in the API by applying the Headers attribute to the interface:

[Headers("User-Agent: Awesome Octocat App")]publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser);}

Dynamic headers

If the content of the header needs to be set at runtime, you can add a header with a dynamic value to a request by applying a Header attribute to a parameter:

[Get("/users/{user}")]Task<User>GetUser(stringuser,[Header("Authorization")]stringauthorization);// Will add the header "Authorization: token OAUTH-TOKEN" to the requestvaruser=awaitGetUser("octocat","token OAUTH-TOKEN");

Authorization (Dynamic Headers redux)

The most common reason to use headers is for authorization. Today most API's use some flavor of oAuth with access tokens that expire and refresh tokens that are longer lived.

One way to encapsulate these kinds of token usage, a custom HttpClientHandler can be inserted instead.

For example:

classAuthenticatedHttpClientHandler:HttpClientHandler{privatereadonlyFunc<Task<string>>getToken;publicAuthenticatedHttpClientHandler(Func<Task<string>>getToken){if(getToken==null)thrownewArgumentNullException(nameof(getToken));this.getToken=getToken;}protectedoverrideasyncTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest,CancellationTokencancellationToken){// See if the request has an authorize headervarauth=request.Headers.Authorization;if(auth!=null){vartoken=awaitgetToken().ConfigureAwait(false);request.Headers.Authorization=newAuthenticationHeaderValue(auth.Scheme,token);}returnawaitbase.SendAsync(request,cancellationToken).ConfigureAwait(false);}}

While HttpClient contains a nearly identical method signature, it is used differently. HttpClient.SendAsync is not called by Refit. The HttpClientHandler must be modified instead.

This class is used like so (example uses the ADAL library to manage auto-token refresh but the principal holds for Xamarin.Auth or any other library:

classLoginViewModel{AuthenticationContextcontext=newAuthenticationContext(...);privateasyncTask<string>GetToken(){// The AcquireTokenAsync call will prompt with a UI if necessary// Or otherwise silently use a refresh token to return// a valid access token	vartoken=awaitcontext.AcquireTokenAsync("http://my.service.uri/app","clientId",newUri("callback://complete"));returntoken;}publicasyncvoidLoginAndCallApi(){varapi=RestService.For<IMyRestService>(newHttpClient(newAuthenticatedHttpClientHandler(GetToken)){BaseAddress=newUri("https://the.end.point/")});varlocation=awaitapi.GetLocationOfRebelBase();}}interfaceIMyRestService{[Get("/getPublicInfo")]Task<Foobar>SomePublicMethod();[Get("/secretStuff")][Headers("Authorization: Bearer")]Task<Location>GetLocationOfRebelBase();}

In the above example, any time a method that requires authentication is called, the AuthenticatedHttpClientHandler will try to get a fresh access token. It's up to the app to provide one, checking the expiration time of an existing access token and obtaining a new one if needed.

Redefining headers

Unlike Retrofit, where headers do not overwrite each other and are all added to the request regardless of how many times the same header is defined, Refit takes a similar approach to the approach ASP.NET MVC takes with action filters — redefining a header will replace it, in the following order of precedence:

  • Headers attribute on the interface (lowest priority)
  • Headers attribute on the method
  • Header attribute on a method parameter (highest priority)
[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")]Task<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji: :smile_cat:")]Task<User>GetUser(stringuser);[Post("/users/new")][Headers("X-Emoji: :metal:")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// X-Emoji: :rocket:varusers=awaitGetUsers();// X-Emoji: :smile_cat:varuser=awaitGetUser("octocat");// X-Emoji: :trollface:awaitCreateUser(user,":trollface:");

Removing headers

Headers defined on an interface or method can be removed by redefining a static header without a value (i.e. without : <value>) or passing null for a dynamic header. Empty strings will be included as empty headers.

[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")][Headers("X-Emoji")]// Remove the X-Emoji headerTask<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji:")]// Redefine the X-Emoji header as emptyTask<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// No X-Emoji headervarusers=awaitGetUsers();// X-Emoji: varuser=awaitGetUser("octocat");// No X-Emoji headerawaitCreateUser(user,null);// X-Emoji: awaitCreateUser(user,"");

Multipart uploads

Methods decorated with Multipart attribute will be submitted with multipart content type. At this time, multipart methods support the following parameter types:

  • string (parameter name will be used as name and string value as value)
  • byte array
  • Stream
  • FileInfo

The parameter name will be used as the name of the field in the multipart data. This can be overridden with the AliasAs attribute.

To specify the file name and content type for byte array (byte[]), Stream and FileInfo parameters, use of a wrapper class is required. The wrapper classes for these types are ByteArrayPart, StreamPart and FileInfoPart.

publicinterfaceISomeApi{[Multipart][Post("/users/{id}/photo")]TaskUploadPhoto(intid,[AliasAs("myPhoto")]StreamPartstream);}

To pass a Stream to this method, construct a StreamPart object like so:

someApiInstance.UploadPhoto(id,newStreamPart(myPhotoStream,"photo.jpg","image/jpeg"));

Note: The AttachmentName attribute that was previously described in this section has been deprecated and its use is not recommended.

Retrieving the response

Note that in Refit unlike in Retrofit, there is no option for a synchronous network request - all requests must be async, either via Task or via IObservable. There is also no option to create an async method via a Callback parameter unlike Retrofit, because we live in the async/await future.

Similarly to how body content changes via the parameter type, the return type will determine the content returned.

Returning Task without a type parameter will discard the content and solely tell you whether or not the call succeeded:

[Post("/users/new")]TaskCreateUser([Body]Useruser);// This will throw if the network call failsawaitCreateUser(someUser);

If the type parameter is 'HttpResponseMessage' or 'string', the raw response message or the content as a string will be returned respectively.

// Returns the content as a string (i.e. the JSON data)[Get("/users/{user}")]Task<string>GetUser(stringuser);// Returns the raw response, as an IObservable that can be used with the// Reactive Extensions[Get("/users/{user}")]IObservable<HttpResponseMessage>GetUser(stringuser);

Using generic interfaces

When using something like ASP.NET Web API, it's a fairly common pattern to have a whole stack of CRUD REST services. Refit now supports these, allowing you to define a single API interface with a generic type:

publicinterfaceIReallyExcitingCrudApi<T,inTKey>whereT:class{[Post("")]Task<T>Create([Body]Tpayload);[Get("")]Task<List<T>>ReadAll();[Get("/{key}")]Task<T>ReadOne(TKeykey);[Put("/{key}")]TaskUpdate(TKeykey,[Body]Tpayload);[Delete("/{key}")]TaskDelete(TKeykey);}

Which can be used like this:

// The "/users" part here is kind of important if you want it to work for more // than one type (unless you have a different domain for each type)varapi=RestService.For<IReallyExcitingCrudApi<User,string>>("http://api.example.com/users");

Using HttpClientFactory

Refit has first class support for the ASP.Net Core 2.1 HttpClientFactory. Add a reference to Refit.HttpClientFactory and call the provided extension method in your ConfigureServices method to configure your Refit interface:

services.AddRefitClient<IWebApi>().ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Optionally, a RefitSettings object can be included:

varsettings=newRefitSettings();// Configure refit settings hereservices.AddRefitClient<IWebApi>(settings).ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Note that some of the properties of RefitSettings will be ignored because the HttpClient and HttpClientHandlers will be managed by the HttpClientFactory instead of Refit.

You can then get the api interface using constructor injection:

publicclassHomeController:Controller{publicHomeController(IWebApiwebApi){_webApi=webApi;}privatereadonlyIWebApi_webApi;publicasyncTask<IActionResult>Index(CancellationTokencancellationToken){varthing=await_webApi.GetSomethingWeNeed(cancellationToken);returnView(thing);}}

About

The automatic type-safe REST library for Xamarin and .NET

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Refit: The automatic type-safe REST library for .NET Core, Xamarin and .NET

Refit is a library heavily inspired by Square's Retrofit library, and it turns your REST API into a live interface:

publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);}

The RestService class generates an implementation of IGitHubApi that uses HttpClient to make its calls:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com");varoctocat=awaitgitHubApi.GetUser("octocat");

Where does this work?

Refit currently supports the following platforms and any .NET Standard 1.4 target:

  • UWP
  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS
  • Desktop .NET 4.6.1
  • .NET Core

Note about .NET Core

For .NET Core build-time support, you must use the .NET Core 2 SDK. You can target any supported platform in your library, long as the 2.0+ SDK is used at build-time.

API Attributes

Every method must have an HTTP attribute that provides the request method and relative URL. There are six built-in annotations: Get, Post, Put, Delete, Patch and Head. The relative URL of the resource is specified in the annotation.

[Get("/users/list")]

You can also specify query parameters in the URL:

[Get("/users/list?sort=desc")]

A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }.

If the name of your parameter doesn't match the name in the URL path, use the AliasAs attribute.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId);

Parameters that are not specified as a URL substitution will automatically be used as query parameters. This is different than Retrofit, where all parameters must be explicitly specified.

The comparison between parameter name and URL parameter is not case-sensitive, so it will work correctly if you name your parameter groupId in the path /group/{groupid}/show for example.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,[AliasAs("sort")]stringsortOrder);GroupList(4,"desc");>>>"/group/4/users?sort=desc"

Dynamic Querystring Parameters

If you specify an object as a query parameter, all public properties which are not null are used as query parameters. Use the Query attribute the change the behavior to 'flatten' your query parameter object. If using this Attribute you can specify values for the Delimiter and the Prefix which are used to 'flatten' the object.

publicclassMyQueryParams{[AliasAs("order")]publicstringSortOrder{get;set;}publicintLimit{get;set;}}[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,MyQueryParamsparams);[Get("/group/{id}/users")]Task<List<User>>GroupListWithAttribute([AliasAs("id")]intgroupId,[Query(".","search")]MyQueryParamsparams);params.SortOrder="desc";params.Limit=10;GroupList(4,params)>>>"/group/4/users?order=desc&Limit=10"
GroupListWithAttribute(4,params)>>>"/group/4/users?search.order=desc&search.Limit=10"

A similar behavior exists if using a Dictionary, but without the advantages of the AliasAs attributes and of course no intellisense and/or type safety.

Collections as Querystring parameters

Use the Query attribute to specify format in which collections should be formatted in query string

[Get("/users/list")]TaskSearch([Query(CollectionFormat.Multi)]int[]ages);Search(new[]{10,20,30})>>>"/users/list?ages=10&ages=20&ages=30"[Get("/users/list")]
Task Search([Query(CollectionFormat.Csv)]int[]ages);
Search(new[]{10,20,30})>>>"/users/list?ages=10%2C20%2C30"

Body content

One of the parameters in your method can be used as the body, by using the Body attribute:

[Post("/users/new")]TaskCreateUser([Body]Useruser);

There are four possibilities for supplying the body data, depending on the type of the parameter:

  • If the type is Stream, the content will be streamed via StreamContent
  • If the type is string, the string will be used directly as the content
  • If the parameter has the attribute [Body(BodySerializationMethod.UrlEncoded)], the content will be URL-encoded (see form posts below)
  • For all other types, the object will be serialized as JSON.

Buffering and the Content-Length header

By default, Refit streams the body content without buffering it. This means you can stream a file from disk, for example, without incurring the overhead of loading the whole file into memory. The downside of this is that no Content-Length header is set on the request. If your API needs you to send a Content-Length header with the request, you can disable this streaming behavior by setting the buffered argument of the [Body] attribute to true:

TaskCreateUser([Body(buffered:true)]Useruser);

JSON content

JSON requests and responses are serialized/deserialized using Json.NET. By default, Refit will use the serializer settings that can be configured by setting Newtonsoft.Json.JsonConvert.DefaultSettings:

JsonConvert.DefaultSettings=()=>newJsonSerializerSettings(){ContractResolver=newCamelCasePropertyNamesContractResolver(),Converters={newStringEnumConverter()}};// Serialized as: {"day":"Saturday"}awaitPostSomeStuff(new{Day=DayOfWeek.Saturday});

As these are global settings they will affect your entire application. It might be beneficial to isolate the settings for calls to a particular API. When creating a Refit generated live interface, you may optionally pass a RefitSettings that will allow you to specify what serializer settings you would like. This allows you to have different serializer settings for separate APIs:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newSnakeCasePropertyNamesContractResolver()}});varotherApi=RestService.For<IOtherApi>("https://api.example.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newCamelCasePropertyNamesContractResolver()}});

Property serialization/deserialization can be customised using Json.NET's JsonProperty attribute:

publicclassFoo{// Works like [AliasAs("b")] would in form posts (see below)[JsonProperty(PropertyName="b")]publicstringBar{get;set;}}

Form posts

For APIs that take form posts (i.e. serialized as application/x-www-form-urlencoded), initialize the Body attribute with BodySerializationMethod.UrlEncoded.

The parameter can be an IDictionary:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Dictionary<string,object>data);}vardata=newDictionary<string,object>{{"v",1},{"tid","UA-1234-5"},{"cid",newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c")},{"t","event"},};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(data);

Or you can just pass any object and all public, readable properties will be serialized as form fields in the request. This approach allows you to alias property names using [AliasAs("whatever")] which can help if the API has cryptic field names:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Measurementmeasurement);}publicclassMeasurement{// Properties can be read-only and [AliasAs] isn't requiredpublicintv{get{return1;}}[AliasAs("tid")]publicstringWebPropertyId{get;set;}[AliasAs("cid")]publicGuidClientId{get;set;}[AliasAs("t")]publicstringType{get;set;}publicobjectIgnoreMe{privateget;set;}}varmeasurement=newMeasurement{WebPropertyId="UA-1234-5",ClientId=newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c"),Type="event"};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(measurement);

If you have a type that has [JsonProperty(PropertyName)] attributes setting property aliases, Refit will use those too ([AliasAs] will take precedence where you have both). This means that the following type will serialize as one=value1&two=value2:

publicclassSomeObject{[JsonProperty(PropertyName="one")]publicstringFirstProperty{get;set;}[JsonProperty(PropertyName="notTwo")][AliasAs("two")]publicstringSecondProperty{get;set;}}

NOTE: This use of AliasAs applies to querystring parameters and form body posts, but not to response objects; for aliasing fields on response objects, you'll still need to use [JsonProperty("full-property-name")].

Setting request headers

Static headers

You can set one or more static request headers for a request applying a Headers attribute to the method:

[Headers("User-Agent: Awesome Octocat App")][Get("/users/{user}")]Task<User>GetUser(stringuser);

Static headers can also be added to every request in the API by applying the Headers attribute to the interface:

[Headers("User-Agent: Awesome Octocat App")]publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser);}

Dynamic headers

If the content of the header needs to be set at runtime, you can add a header with a dynamic value to a request by applying a Header attribute to a parameter:

[Get("/users/{user}")]Task<User>GetUser(stringuser,[Header("Authorization")]stringauthorization);// Will add the header "Authorization: token OAUTH-TOKEN" to the requestvaruser=awaitGetUser("octocat","token OAUTH-TOKEN");

Authorization (Dynamic Headers redux)

The most common reason to use headers is for authorization. Today most API's use some flavor of oAuth with access tokens that expire and refresh tokens that are longer lived.

One way to encapsulate these kinds of token usage, a custom HttpClientHandler can be inserted instead.

For example:

classAuthenticatedHttpClientHandler:HttpClientHandler{privatereadonlyFunc<Task<string>>getToken;publicAuthenticatedHttpClientHandler(Func<Task<string>>getToken){if(getToken==null)thrownewArgumentNullException(nameof(getToken));this.getToken=getToken;}protectedoverrideasyncTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest,CancellationTokencancellationToken){// See if the request has an authorize headervarauth=request.Headers.Authorization;if(auth!=null){vartoken=awaitgetToken().ConfigureAwait(false);request.Headers.Authorization=newAuthenticationHeaderValue(auth.Scheme,token);}returnawaitbase.SendAsync(request,cancellationToken).ConfigureAwait(false);}}

While HttpClient contains a nearly identical method signature, it is used differently. HttpClient.SendAsync is not called by Refit. The HttpClientHandler must be modified instead.

This class is used like so (example uses the ADAL library to manage auto-token refresh but the principal holds for Xamarin.Auth or any other library:

classLoginViewModel{AuthenticationContextcontext=newAuthenticationContext(...);privateasyncTask<string>GetToken(){// The AcquireTokenAsync call will prompt with a UI if necessary// Or otherwise silently use a refresh token to return// a valid access token	vartoken=awaitcontext.AcquireTokenAsync("http://my.service.uri/app","clientId",newUri("callback://complete"));returntoken;}publicasyncvoidLoginAndCallApi(){varapi=RestService.For<IMyRestService>(newHttpClient(newAuthenticatedHttpClientHandler(GetToken)){BaseAddress=newUri("https://the.end.point/")});varlocation=awaitapi.GetLocationOfRebelBase();}}interfaceIMyRestService{[Get("/getPublicInfo")]Task<Foobar>SomePublicMethod();[Get("/secretStuff")][Headers("Authorization: Bearer")]Task<Location>GetLocationOfRebelBase();}

In the above example, any time a method that requires authentication is called, the AuthenticatedHttpClientHandler will try to get a fresh access token. It's up to the app to provide one, checking the expiration time of an existing access token and obtaining a new one if needed.

Redefining headers

Unlike Retrofit, where headers do not overwrite each other and are all added to the request regardless of how many times the same header is defined, Refit takes a similar approach to the approach ASP.NET MVC takes with action filters — redefining a header will replace it, in the following order of precedence:

  • Headers attribute on the interface (lowest priority)
  • Headers attribute on the method
  • Header attribute on a method parameter (highest priority)
[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")]Task<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji: :smile_cat:")]Task<User>GetUser(stringuser);[Post("/users/new")][Headers("X-Emoji: :metal:")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// X-Emoji: :rocket:varusers=awaitGetUsers();// X-Emoji: :smile_cat:varuser=awaitGetUser("octocat");// X-Emoji: :trollface:awaitCreateUser(user,":trollface:");

Removing headers

Headers defined on an interface or method can be removed by redefining a static header without a value (i.e. without : <value>) or passing null for a dynamic header. Empty strings will be included as empty headers.

[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")][Headers("X-Emoji")]// Remove the X-Emoji headerTask<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji:")]// Redefine the X-Emoji header as emptyTask<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// No X-Emoji headervarusers=awaitGetUsers();// X-Emoji: varuser=awaitGetUser("octocat");// No X-Emoji headerawaitCreateUser(user,null);// X-Emoji: awaitCreateUser(user,"");

Multipart uploads

Methods decorated with Multipart attribute will be submitted with multipart content type. At this time, multipart methods support the following parameter types:

  • string (parameter name will be used as name and string value as value)
  • byte array
  • Stream
  • FileInfo

The parameter name will be used as the name of the field in the multipart data. This can be overridden with the AliasAs attribute.

To specify the file name and content type for byte array (byte[]), Stream and FileInfo parameters, use of a wrapper class is required. The wrapper classes for these types are ByteArrayPart, StreamPart and FileInfoPart.

publicinterfaceISomeApi{[Multipart][Post("/users/{id}/photo")]TaskUploadPhoto(intid,[AliasAs("myPhoto")]StreamPartstream);}

To pass a Stream to this method, construct a StreamPart object like so:

someApiInstance.UploadPhoto(id,newStreamPart(myPhotoStream,"photo.jpg","image/jpeg"));

Note: The AttachmentName attribute that was previously described in this section has been deprecated and its use is not recommended.

Retrieving the response

Note that in Refit unlike in Retrofit, there is no option for a synchronous network request - all requests must be async, either via Task or via IObservable. There is also no option to create an async method via a Callback parameter unlike Retrofit, because we live in the async/await future.

Similarly to how body content changes via the parameter type, the return type will determine the content returned.

Returning Task without a type parameter will discard the content and solely tell you whether or not the call succeeded:

[Post("/users/new")]TaskCreateUser([Body]Useruser);// This will throw if the network call failsawaitCreateUser(someUser);

If the type parameter is 'HttpResponseMessage' or 'string', the raw response message or the content as a string will be returned respectively.

// Returns the content as a string (i.e. the JSON data)[Get("/users/{user}")]Task<string>GetUser(stringuser);// Returns the raw response, as an IObservable that can be used with the// Reactive Extensions[Get("/users/{user}")]IObservable<HttpResponseMessage>GetUser(stringuser);

Using generic interfaces

When using something like ASP.NET Web API, it's a fairly common pattern to have a whole stack of CRUD REST services. Refit now supports these, allowing you to define a single API interface with a generic type:

publicinterfaceIReallyExcitingCrudApi<T,inTKey>whereT:class{[Post("")]Task<T>Create([Body]Tpayload);[Get("")]Task<List<T>>ReadAll();[Get("/{key}")]Task<T>ReadOne(TKeykey);[Put("/{key}")]TaskUpdate(TKeykey,[Body]Tpayload);[Delete("/{key}")]TaskDelete(TKeykey);}

Which can be used like this:

// The "/users" part here is kind of important if you want it to work for more // than one type (unless you have a different domain for each type)varapi=RestService.For<IReallyExcitingCrudApi<User,string>>("http://api.example.com/users");

Using HttpClientFactory

Refit has first class support for the ASP.Net Core 2.1 HttpClientFactory. Add a reference to Refit.HttpClientFactory and call the provided extension method in your ConfigureServices method to configure your Refit interface:

services.AddRefitClient<IWebApi>().ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Optionally, a RefitSettings object can be included:

varsettings=newRefitSettings();// Configure refit settings hereservices.AddRefitClient<IWebApi>(settings).ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Note that some of the properties of RefitSettings will be ignored because the HttpClient and HttpClientHandlers will be managed by the HttpClientFactory instead of Refit.

You can then get the api interface using constructor injection:

publicclassHomeController:Controller{publicHomeController(IWebApiwebApi){_webApi=webApi;}privatereadonlyIWebApi_webApi;publicasyncTask<IActionResult>Index(CancellationTokencancellationToken){varthing=await_webApi.GetSomethingWeNeed(cancellationToken);returnView(thing);}}

About

The automatic type-safe REST library for Xamarin and .NET

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Refit: The automatic type-safe REST library for .NET Core, Xamarin and .NET

Refit is a library heavily inspired by Square's Retrofit library, and it turns your REST API into a live interface:

publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);}

The RestService class generates an implementation of IGitHubApi that uses HttpClient to make its calls:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com");varoctocat=awaitgitHubApi.GetUser("octocat");

Where does this work?

Refit currently supports the following platforms and any .NET Standard 1.4 target:

  • UWP
  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS
  • Desktop .NET 4.6.1
  • .NET Core

Note about .NET Core

For .NET Core build-time support, you must use the .NET Core 2 SDK. You can target any supported platform in your library, long as the 2.0+ SDK is used at build-time.

API Attributes

Every method must have an HTTP attribute that provides the request method and relative URL. There are six built-in annotations: Get, Post, Put, Delete, Patch and Head. The relative URL of the resource is specified in the annotation.

[Get("/users/list")]

You can also specify query parameters in the URL:

[Get("/users/list?sort=desc")]

A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }.

If the name of your parameter doesn't match the name in the URL path, use the AliasAs attribute.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId);

Parameters that are not specified as a URL substitution will automatically be used as query parameters. This is different than Retrofit, where all parameters must be explicitly specified.

The comparison between parameter name and URL parameter is not case-sensitive, so it will work correctly if you name your parameter groupId in the path /group/{groupid}/show for example.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,[AliasAs("sort")]stringsortOrder);GroupList(4,"desc");>>>"/group/4/users?sort=desc"

Dynamic Querystring Parameters

If you specify an object as a query parameter, all public properties which are not null are used as query parameters. Use the Query attribute the change the behavior to 'flatten' your query parameter object. If using this Attribute you can specify values for the Delimiter and the Prefix which are used to 'flatten' the object.

publicclassMyQueryParams{[AliasAs("order")]publicstringSortOrder{get;set;}publicintLimit{get;set;}}[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,MyQueryParamsparams);[Get("/group/{id}/users")]Task<List<User>>GroupListWithAttribute([AliasAs("id")]intgroupId,[Query(".","search")]MyQueryParamsparams);params.SortOrder="desc";params.Limit=10;GroupList(4,params)>>>"/group/4/users?order=desc&Limit=10"
GroupListWithAttribute(4,params)>>>"/group/4/users?search.order=desc&search.Limit=10"

A similar behavior exists if using a Dictionary, but without the advantages of the AliasAs attributes and of course no intellisense and/or type safety.

Collections as Querystring parameters

Use the Query attribute to specify format in which collections should be formatted in query string

[Get("/users/list")]TaskSearch([Query(CollectionFormat.Multi)]int[]ages);Search(new[]{10,20,30})>>>"/users/list?ages=10&ages=20&ages=30"[Get("/users/list")]
Task Search([Query(CollectionFormat.Csv)]int[]ages);
Search(new[]{10,20,30})>>>"/users/list?ages=10%2C20%2C30"

Body content

One of the parameters in your method can be used as the body, by using the Body attribute:

[Post("/users/new")]TaskCreateUser([Body]Useruser);

There are four possibilities for supplying the body data, depending on the type of the parameter:

  • If the type is Stream, the content will be streamed via StreamContent
  • If the type is string, the string will be used directly as the content
  • If the parameter has the attribute [Body(BodySerializationMethod.UrlEncoded)], the content will be URL-encoded (see form posts below)
  • For all other types, the object will be serialized as JSON.

Buffering and the Content-Length header

By default, Refit streams the body content without buffering it. This means you can stream a file from disk, for example, without incurring the overhead of loading the whole file into memory. The downside of this is that no Content-Length header is set on the request. If your API needs you to send a Content-Length header with the request, you can disable this streaming behavior by setting the buffered argument of the [Body] attribute to true:

TaskCreateUser([Body(buffered:true)]Useruser);

JSON content

JSON requests and responses are serialized/deserialized using Json.NET. By default, Refit will use the serializer settings that can be configured by setting Newtonsoft.Json.JsonConvert.DefaultSettings:

JsonConvert.DefaultSettings=()=>newJsonSerializerSettings(){ContractResolver=newCamelCasePropertyNamesContractResolver(),Converters={newStringEnumConverter()}};// Serialized as: {"day":"Saturday"}awaitPostSomeStuff(new{Day=DayOfWeek.Saturday});

As these are global settings they will affect your entire application. It might be beneficial to isolate the settings for calls to a particular API. When creating a Refit generated live interface, you may optionally pass a RefitSettings that will allow you to specify what serializer settings you would like. This allows you to have different serializer settings for separate APIs:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newSnakeCasePropertyNamesContractResolver()}});varotherApi=RestService.For<IOtherApi>("https://api.example.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newCamelCasePropertyNamesContractResolver()}});

Property serialization/deserialization can be customised using Json.NET's JsonProperty attribute:

publicclassFoo{// Works like [AliasAs("b")] would in form posts (see below)[JsonProperty(PropertyName="b")]publicstringBar{get;set;}}

Form posts

For APIs that take form posts (i.e. serialized as application/x-www-form-urlencoded), initialize the Body attribute with BodySerializationMethod.UrlEncoded.

The parameter can be an IDictionary:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Dictionary<string,object>data);}vardata=newDictionary<string,object>{{"v",1},{"tid","UA-1234-5"},{"cid",newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c")},{"t","event"},};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(data);

Or you can just pass any object and all public, readable properties will be serialized as form fields in the request. This approach allows you to alias property names using [AliasAs("whatever")] which can help if the API has cryptic field names:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Measurementmeasurement);}publicclassMeasurement{// Properties can be read-only and [AliasAs] isn't requiredpublicintv{get{return1;}}[AliasAs("tid")]publicstringWebPropertyId{get;set;}[AliasAs("cid")]publicGuidClientId{get;set;}[AliasAs("t")]publicstringType{get;set;}publicobjectIgnoreMe{privateget;set;}}varmeasurement=newMeasurement{WebPropertyId="UA-1234-5",ClientId=newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c"),Type="event"};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(measurement);

If you have a type that has [JsonProperty(PropertyName)] attributes setting property aliases, Refit will use those too ([AliasAs] will take precedence where you have both). This means that the following type will serialize as one=value1&two=value2:

publicclassSomeObject{[JsonProperty(PropertyName="one")]publicstringFirstProperty{get;set;}[JsonProperty(PropertyName="notTwo")][AliasAs("two")]publicstringSecondProperty{get;set;}}

NOTE: This use of AliasAs applies to querystring parameters and form body posts, but not to response objects; for aliasing fields on response objects, you'll still need to use [JsonProperty("full-property-name")].

Setting request headers

Static headers

You can set one or more static request headers for a request applying a Headers attribute to the method:

[Headers("User-Agent: Awesome Octocat App")][Get("/users/{user}")]Task<User>GetUser(stringuser);

Static headers can also be added to every request in the API by applying the Headers attribute to the interface:

[Headers("User-Agent: Awesome Octocat App")]publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser);}

Dynamic headers

If the content of the header needs to be set at runtime, you can add a header with a dynamic value to a request by applying a Header attribute to a parameter:

[Get("/users/{user}")]Task<User>GetUser(stringuser,[Header("Authorization")]stringauthorization);// Will add the header "Authorization: token OAUTH-TOKEN" to the requestvaruser=awaitGetUser("octocat","token OAUTH-TOKEN");

Authorization (Dynamic Headers redux)

The most common reason to use headers is for authorization. Today most API's use some flavor of oAuth with access tokens that expire and refresh tokens that are longer lived.

One way to encapsulate these kinds of token usage, a custom HttpClientHandler can be inserted instead.

For example:

classAuthenticatedHttpClientHandler:HttpClientHandler{privatereadonlyFunc<Task<string>>getToken;publicAuthenticatedHttpClientHandler(Func<Task<string>>getToken){if(getToken==null)thrownewArgumentNullException(nameof(getToken));this.getToken=getToken;}protectedoverrideasyncTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest,CancellationTokencancellationToken){// See if the request has an authorize headervarauth=request.Headers.Authorization;if(auth!=null){vartoken=awaitgetToken().ConfigureAwait(false);request.Headers.Authorization=newAuthenticationHeaderValue(auth.Scheme,token);}returnawaitbase.SendAsync(request,cancellationToken).ConfigureAwait(false);}}

While HttpClient contains a nearly identical method signature, it is used differently. HttpClient.SendAsync is not called by Refit. The HttpClientHandler must be modified instead.

This class is used like so (example uses the ADAL library to manage auto-token refresh but the principal holds for Xamarin.Auth or any other library:

classLoginViewModel{AuthenticationContextcontext=newAuthenticationContext(...);privateasyncTask<string>GetToken(){// The AcquireTokenAsync call will prompt with a UI if necessary// Or otherwise silently use a refresh token to return// a valid access token	vartoken=awaitcontext.AcquireTokenAsync("http://my.service.uri/app","clientId",newUri("callback://complete"));returntoken;}publicasyncvoidLoginAndCallApi(){varapi=RestService.For<IMyRestService>(newHttpClient(newAuthenticatedHttpClientHandler(GetToken)){BaseAddress=newUri("https://the.end.point/")});varlocation=awaitapi.GetLocationOfRebelBase();}}interfaceIMyRestService{[Get("/getPublicInfo")]Task<Foobar>SomePublicMethod();[Get("/secretStuff")][Headers("Authorization: Bearer")]Task<Location>GetLocationOfRebelBase();}

In the above example, any time a method that requires authentication is called, the AuthenticatedHttpClientHandler will try to get a fresh access token. It's up to the app to provide one, checking the expiration time of an existing access token and obtaining a new one if needed.

Redefining headers

Unlike Retrofit, where headers do not overwrite each other and are all added to the request regardless of how many times the same header is defined, Refit takes a similar approach to the approach ASP.NET MVC takes with action filters — redefining a header will replace it, in the following order of precedence:

  • Headers attribute on the interface (lowest priority)
  • Headers attribute on the method
  • Header attribute on a method parameter (highest priority)
[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")]Task<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji: :smile_cat:")]Task<User>GetUser(stringuser);[Post("/users/new")][Headers("X-Emoji: :metal:")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// X-Emoji: :rocket:varusers=awaitGetUsers();// X-Emoji: :smile_cat:varuser=awaitGetUser("octocat");// X-Emoji: :trollface:awaitCreateUser(user,":trollface:");

Removing headers

Headers defined on an interface or method can be removed by redefining a static header without a value (i.e. without : <value>) or passing null for a dynamic header. Empty strings will be included as empty headers.

[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")][Headers("X-Emoji")]// Remove the X-Emoji headerTask<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji:")]// Redefine the X-Emoji header as emptyTask<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// No X-Emoji headervarusers=awaitGetUsers();// X-Emoji: varuser=awaitGetUser("octocat");// No X-Emoji headerawaitCreateUser(user,null);// X-Emoji: awaitCreateUser(user,"");

Multipart uploads

Methods decorated with Multipart attribute will be submitted with multipart content type. At this time, multipart methods support the following parameter types:

  • string (parameter name will be used as name and string value as value)
  • byte array
  • Stream
  • FileInfo

The parameter name will be used as the name of the field in the multipart data. This can be overridden with the AliasAs attribute.

To specify the file name and content type for byte array (byte[]), Stream and FileInfo parameters, use of a wrapper class is required. The wrapper classes for these types are ByteArrayPart, StreamPart and FileInfoPart.

publicinterfaceISomeApi{[Multipart][Post("/users/{id}/photo")]TaskUploadPhoto(intid,[AliasAs("myPhoto")]StreamPartstream);}

To pass a Stream to this method, construct a StreamPart object like so:

someApiInstance.UploadPhoto(id,newStreamPart(myPhotoStream,"photo.jpg","image/jpeg"));

Note: The AttachmentName attribute that was previously described in this section has been deprecated and its use is not recommended.

Retrieving the response

Note that in Refit unlike in Retrofit, there is no option for a synchronous network request - all requests must be async, either via Task or via IObservable. There is also no option to create an async method via a Callback parameter unlike Retrofit, because we live in the async/await future.

Similarly to how body content changes via the parameter type, the return type will determine the content returned.

Returning Task without a type parameter will discard the content and solely tell you whether or not the call succeeded:

[Post("/users/new")]TaskCreateUser([Body]Useruser);// This will throw if the network call failsawaitCreateUser(someUser);

If the type parameter is 'HttpResponseMessage' or 'string', the raw response message or the content as a string will be returned respectively.

// Returns the content as a string (i.e. the JSON data)[Get("/users/{user}")]Task<string>GetUser(stringuser);// Returns the raw response, as an IObservable that can be used with the// Reactive Extensions[Get("/users/{user}")]IObservable<HttpResponseMessage>GetUser(stringuser);

Using generic interfaces

When using something like ASP.NET Web API, it's a fairly common pattern to have a whole stack of CRUD REST services. Refit now supports these, allowing you to define a single API interface with a generic type:

publicinterfaceIReallyExcitingCrudApi<T,inTKey>whereT:class{[Post("")]Task<T>Create([Body]Tpayload);[Get("")]Task<List<T>>ReadAll();[Get("/{key}")]Task<T>ReadOne(TKeykey);[Put("/{key}")]TaskUpdate(TKeykey,[Body]Tpayload);[Delete("/{key}")]TaskDelete(TKeykey);}

Which can be used like this:

// The "/users" part here is kind of important if you want it to work for more // than one type (unless you have a different domain for each type)varapi=RestService.For<IReallyExcitingCrudApi<User,string>>("http://api.example.com/users");

Using HttpClientFactory

Refit has first class support for the ASP.Net Core 2.1 HttpClientFactory. Add a reference to Refit.HttpClientFactory and call the provided extension method in your ConfigureServices method to configure your Refit interface:

services.AddRefitClient<IWebApi>().ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Optionally, a RefitSettings object can be included:

varsettings=newRefitSettings();// Configure refit settings hereservices.AddRefitClient<IWebApi>(settings).ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Note that some of the properties of RefitSettings will be ignored because the HttpClient and HttpClientHandlers will be managed by the HttpClientFactory instead of Refit.

You can then get the api interface using constructor injection:

publicclassHomeController:Controller{publicHomeController(IWebApiwebApi){_webApi=webApi;}privatereadonlyIWebApi_webApi;publicasyncTask<IActionResult>Index(CancellationTokencancellationToken){varthing=await_webApi.GetSomethingWeNeed(cancellationToken);returnView(thing);}}

About

The automatic type-safe REST library for Xamarin and .NET

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Refit: The automatic type-safe REST library for .NET Core, Xamarin and .NET

Refit is a library heavily inspired by Square's Retrofit library, and it turns your REST API into a live interface:

publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);}

The RestService class generates an implementation of IGitHubApi that uses HttpClient to make its calls:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com");varoctocat=awaitgitHubApi.GetUser("octocat");

Where does this work?

Refit currently supports the following platforms and any .NET Standard 1.4 target:

  • UWP
  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS
  • Desktop .NET 4.6.1
  • .NET Core

Note about .NET Core

For .NET Core build-time support, you must use the .NET Core 2 SDK. You can target any supported platform in your library, long as the 2.0+ SDK is used at build-time.

API Attributes

Every method must have an HTTP attribute that provides the request method and relative URL. There are six built-in annotations: Get, Post, Put, Delete, Patch and Head. The relative URL of the resource is specified in the annotation.

[Get("/users/list")]

You can also specify query parameters in the URL:

[Get("/users/list?sort=desc")]

A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }.

If the name of your parameter doesn't match the name in the URL path, use the AliasAs attribute.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId);

Parameters that are not specified as a URL substitution will automatically be used as query parameters. This is different than Retrofit, where all parameters must be explicitly specified.

The comparison between parameter name and URL parameter is not case-sensitive, so it will work correctly if you name your parameter groupId in the path /group/{groupid}/show for example.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,[AliasAs("sort")]stringsortOrder);GroupList(4,"desc");>>>"/group/4/users?sort=desc"

Dynamic Querystring Parameters

If you specify an object as a query parameter, all public properties which are not null are used as query parameters. Use the Query attribute the change the behavior to 'flatten' your query parameter object. If using this Attribute you can specify values for the Delimiter and the Prefix which are used to 'flatten' the object.

publicclassMyQueryParams{[AliasAs("order")]publicstringSortOrder{get;set;}publicintLimit{get;set;}}[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,MyQueryParamsparams);[Get("/group/{id}/users")]Task<List<User>>GroupListWithAttribute([AliasAs("id")]intgroupId,[Query(".","search")]MyQueryParamsparams);params.SortOrder="desc";params.Limit=10;GroupList(4,params)>>>"/group/4/users?order=desc&Limit=10"
GroupListWithAttribute(4,params)>>>"/group/4/users?search.order=desc&search.Limit=10"

A similar behavior exists if using a Dictionary, but without the advantages of the AliasAs attributes and of course no intellisense and/or type safety.

Collections as Querystring parameters

Use the Query attribute to specify format in which collections should be formatted in query string

[Get("/users/list")]TaskSearch([Query(CollectionFormat.Multi)]int[]ages);Search(new[]{10,20,30})>>>"/users/list?ages=10&ages=20&ages=30"[Get("/users/list")]
Task Search([Query(CollectionFormat.Csv)]int[]ages);
Search(new[]{10,20,30})>>>"/users/list?ages=10%2C20%2C30"

Body content

One of the parameters in your method can be used as the body, by using the Body attribute:

[Post("/users/new")]TaskCreateUser([Body]Useruser);

There are four possibilities for supplying the body data, depending on the type of the parameter:

  • If the type is Stream, the content will be streamed via StreamContent
  • If the type is string, the string will be used directly as the content
  • If the parameter has the attribute [Body(BodySerializationMethod.UrlEncoded)], the content will be URL-encoded (see form posts below)
  • For all other types, the object will be serialized as JSON.

Buffering and the Content-Length header

By default, Refit streams the body content without buffering it. This means you can stream a file from disk, for example, without incurring the overhead of loading the whole file into memory. The downside of this is that no Content-Length header is set on the request. If your API needs you to send a Content-Length header with the request, you can disable this streaming behavior by setting the buffered argument of the [Body] attribute to true:

TaskCreateUser([Body(buffered:true)]Useruser);

JSON content

JSON requests and responses are serialized/deserialized using Json.NET. By default, Refit will use the serializer settings that can be configured by setting Newtonsoft.Json.JsonConvert.DefaultSettings:

JsonConvert.DefaultSettings=()=>newJsonSerializerSettings(){ContractResolver=newCamelCasePropertyNamesContractResolver(),Converters={newStringEnumConverter()}};// Serialized as: {"day":"Saturday"}awaitPostSomeStuff(new{Day=DayOfWeek.Saturday});

As these are global settings they will affect your entire application. It might be beneficial to isolate the settings for calls to a particular API. When creating a Refit generated live interface, you may optionally pass a RefitSettings that will allow you to specify what serializer settings you would like. This allows you to have different serializer settings for separate APIs:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newSnakeCasePropertyNamesContractResolver()}});varotherApi=RestService.For<IOtherApi>("https://api.example.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newCamelCasePropertyNamesContractResolver()}});

Property serialization/deserialization can be customised using Json.NET's JsonProperty attribute:

publicclassFoo{// Works like [AliasAs("b")] would in form posts (see below)[JsonProperty(PropertyName="b")]publicstringBar{get;set;}}

Form posts

For APIs that take form posts (i.e. serialized as application/x-www-form-urlencoded), initialize the Body attribute with BodySerializationMethod.UrlEncoded.

The parameter can be an IDictionary:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Dictionary<string,object>data);}vardata=newDictionary<string,object>{{"v",1},{"tid","UA-1234-5"},{"cid",newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c")},{"t","event"},};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(data);

Or you can just pass any object and all public, readable properties will be serialized as form fields in the request. This approach allows you to alias property names using [AliasAs("whatever")] which can help if the API has cryptic field names:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Measurementmeasurement);}publicclassMeasurement{// Properties can be read-only and [AliasAs] isn't requiredpublicintv{get{return1;}}[AliasAs("tid")]publicstringWebPropertyId{get;set;}[AliasAs("cid")]publicGuidClientId{get;set;}[AliasAs("t")]publicstringType{get;set;}publicobjectIgnoreMe{privateget;set;}}varmeasurement=newMeasurement{WebPropertyId="UA-1234-5",ClientId=newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c"),Type="event"};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(measurement);

If you have a type that has [JsonProperty(PropertyName)] attributes setting property aliases, Refit will use those too ([AliasAs] will take precedence where you have both). This means that the following type will serialize as one=value1&two=value2:

publicclassSomeObject{[JsonProperty(PropertyName="one")]publicstringFirstProperty{get;set;}[JsonProperty(PropertyName="notTwo")][AliasAs("two")]publicstringSecondProperty{get;set;}}

NOTE: This use of AliasAs applies to querystring parameters and form body posts, but not to response objects; for aliasing fields on response objects, you'll still need to use [JsonProperty("full-property-name")].

Setting request headers

Static headers

You can set one or more static request headers for a request applying a Headers attribute to the method:

[Headers("User-Agent: Awesome Octocat App")][Get("/users/{user}")]Task<User>GetUser(stringuser);

Static headers can also be added to every request in the API by applying the Headers attribute to the interface:

[Headers("User-Agent: Awesome Octocat App")]publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser);}

Dynamic headers

If the content of the header needs to be set at runtime, you can add a header with a dynamic value to a request by applying a Header attribute to a parameter:

[Get("/users/{user}")]Task<User>GetUser(stringuser,[Header("Authorization")]stringauthorization);// Will add the header "Authorization: token OAUTH-TOKEN" to the requestvaruser=awaitGetUser("octocat","token OAUTH-TOKEN");

Authorization (Dynamic Headers redux)

The most common reason to use headers is for authorization. Today most API's use some flavor of oAuth with access tokens that expire and refresh tokens that are longer lived.

One way to encapsulate these kinds of token usage, a custom HttpClientHandler can be inserted instead.

For example:

classAuthenticatedHttpClientHandler:HttpClientHandler{privatereadonlyFunc<Task<string>>getToken;publicAuthenticatedHttpClientHandler(Func<Task<string>>getToken){if(getToken==null)thrownewArgumentNullException(nameof(getToken));this.getToken=getToken;}protectedoverrideasyncTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest,CancellationTokencancellationToken){// See if the request has an authorize headervarauth=request.Headers.Authorization;if(auth!=null){vartoken=awaitgetToken().ConfigureAwait(false);request.Headers.Authorization=newAuthenticationHeaderValue(auth.Scheme,token);}returnawaitbase.SendAsync(request,cancellationToken).ConfigureAwait(false);}}

While HttpClient contains a nearly identical method signature, it is used differently. HttpClient.SendAsync is not called by Refit. The HttpClientHandler must be modified instead.

This class is used like so (example uses the ADAL library to manage auto-token refresh but the principal holds for Xamarin.Auth or any other library:

classLoginViewModel{AuthenticationContextcontext=newAuthenticationContext(...);privateasyncTask<string>GetToken(){// The AcquireTokenAsync call will prompt with a UI if necessary// Or otherwise silently use a refresh token to return// a valid access token	vartoken=awaitcontext.AcquireTokenAsync("http://my.service.uri/app","clientId",newUri("callback://complete"));returntoken;}publicasyncvoidLoginAndCallApi(){varapi=RestService.For<IMyRestService>(newHttpClient(newAuthenticatedHttpClientHandler(GetToken)){BaseAddress=newUri("https://the.end.point/")});varlocation=awaitapi.GetLocationOfRebelBase();}}interfaceIMyRestService{[Get("/getPublicInfo")]Task<Foobar>SomePublicMethod();[Get("/secretStuff")][Headers("Authorization: Bearer")]Task<Location>GetLocationOfRebelBase();}

In the above example, any time a method that requires authentication is called, the AuthenticatedHttpClientHandler will try to get a fresh access token. It's up to the app to provide one, checking the expiration time of an existing access token and obtaining a new one if needed.

Redefining headers

Unlike Retrofit, where headers do not overwrite each other and are all added to the request regardless of how many times the same header is defined, Refit takes a similar approach to the approach ASP.NET MVC takes with action filters — redefining a header will replace it, in the following order of precedence:

  • Headers attribute on the interface (lowest priority)
  • Headers attribute on the method
  • Header attribute on a method parameter (highest priority)
[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")]Task<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji: :smile_cat:")]Task<User>GetUser(stringuser);[Post("/users/new")][Headers("X-Emoji: :metal:")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// X-Emoji: :rocket:varusers=awaitGetUsers();// X-Emoji: :smile_cat:varuser=awaitGetUser("octocat");// X-Emoji: :trollface:awaitCreateUser(user,":trollface:");

Removing headers

Headers defined on an interface or method can be removed by redefining a static header without a value (i.e. without : <value>) or passing null for a dynamic header. Empty strings will be included as empty headers.

[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")][Headers("X-Emoji")]// Remove the X-Emoji headerTask<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji:")]// Redefine the X-Emoji header as emptyTask<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// No X-Emoji headervarusers=awaitGetUsers();// X-Emoji: varuser=awaitGetUser("octocat");// No X-Emoji headerawaitCreateUser(user,null);// X-Emoji: awaitCreateUser(user,"");

Multipart uploads

Methods decorated with Multipart attribute will be submitted with multipart content type. At this time, multipart methods support the following parameter types:

  • string (parameter name will be used as name and string value as value)
  • byte array
  • Stream
  • FileInfo

The parameter name will be used as the name of the field in the multipart data. This can be overridden with the AliasAs attribute.

To specify the file name and content type for byte array (byte[]), Stream and FileInfo parameters, use of a wrapper class is required. The wrapper classes for these types are ByteArrayPart, StreamPart and FileInfoPart.

publicinterfaceISomeApi{[Multipart][Post("/users/{id}/photo")]TaskUploadPhoto(intid,[AliasAs("myPhoto")]StreamPartstream);}

To pass a Stream to this method, construct a StreamPart object like so:

someApiInstance.UploadPhoto(id,newStreamPart(myPhotoStream,"photo.jpg","image/jpeg"));

Note: The AttachmentName attribute that was previously described in this section has been deprecated and its use is not recommended.

Retrieving the response

Note that in Refit unlike in Retrofit, there is no option for a synchronous network request - all requests must be async, either via Task or via IObservable. There is also no option to create an async method via a Callback parameter unlike Retrofit, because we live in the async/await future.

Similarly to how body content changes via the parameter type, the return type will determine the content returned.

Returning Task without a type parameter will discard the content and solely tell you whether or not the call succeeded:

[Post("/users/new")]TaskCreateUser([Body]Useruser);// This will throw if the network call failsawaitCreateUser(someUser);

If the type parameter is 'HttpResponseMessage' or 'string', the raw response message or the content as a string will be returned respectively.

// Returns the content as a string (i.e. the JSON data)[Get("/users/{user}")]Task<string>GetUser(stringuser);// Returns the raw response, as an IObservable that can be used with the// Reactive Extensions[Get("/users/{user}")]IObservable<HttpResponseMessage>GetUser(stringuser);

Using generic interfaces

When using something like ASP.NET Web API, it's a fairly common pattern to have a whole stack of CRUD REST services. Refit now supports these, allowing you to define a single API interface with a generic type:

publicinterfaceIReallyExcitingCrudApi<T,inTKey>whereT:class{[Post("")]Task<T>Create([Body]Tpayload);[Get("")]Task<List<T>>ReadAll();[Get("/{key}")]Task<T>ReadOne(TKeykey);[Put("/{key}")]TaskUpdate(TKeykey,[Body]Tpayload);[Delete("/{key}")]TaskDelete(TKeykey);}

Which can be used like this:

// The "/users" part here is kind of important if you want it to work for more // than one type (unless you have a different domain for each type)varapi=RestService.For<IReallyExcitingCrudApi<User,string>>("http://api.example.com/users");

Using HttpClientFactory

Refit has first class support for the ASP.Net Core 2.1 HttpClientFactory. Add a reference to Refit.HttpClientFactory and call the provided extension method in your ConfigureServices method to configure your Refit interface:

services.AddRefitClient<IWebApi>().ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Optionally, a RefitSettings object can be included:

varsettings=newRefitSettings();// Configure refit settings hereservices.AddRefitClient<IWebApi>(settings).ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Note that some of the properties of RefitSettings will be ignored because the HttpClient and HttpClientHandlers will be managed by the HttpClientFactory instead of Refit.

You can then get the api interface using constructor injection:

publicclassHomeController:Controller{publicHomeController(IWebApiwebApi){_webApi=webApi;}privatereadonlyIWebApi_webApi;publicasyncTask<IActionResult>Index(CancellationTokencancellationToken){varthing=await_webApi.GetSomethingWeNeed(cancellationToken);returnView(thing);}}

About

The automatic type-safe REST library for Xamarin and .NET

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Refit: The automatic type-safe REST library for .NET Core, Xamarin and .NET

Refit is a library heavily inspired by Square's Retrofit library, and it turns your REST API into a live interface:

publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);}

The RestService class generates an implementation of IGitHubApi that uses HttpClient to make its calls:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com");varoctocat=awaitgitHubApi.GetUser("octocat");

Where does this work?

Refit currently supports the following platforms and any .NET Standard 1.4 target:

  • UWP
  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS
  • Desktop .NET 4.6.1
  • .NET Core

Note about .NET Core

For .NET Core build-time support, you must use the .NET Core 2 SDK. You can target any supported platform in your library, long as the 2.0+ SDK is used at build-time.

API Attributes

Every method must have an HTTP attribute that provides the request method and relative URL. There are six built-in annotations: Get, Post, Put, Delete, Patch and Head. The relative URL of the resource is specified in the annotation.

[Get("/users/list")]

You can also specify query parameters in the URL:

[Get("/users/list?sort=desc")]

A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }.

If the name of your parameter doesn't match the name in the URL path, use the AliasAs attribute.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId);

Parameters that are not specified as a URL substitution will automatically be used as query parameters. This is different than Retrofit, where all parameters must be explicitly specified.

The comparison between parameter name and URL parameter is not case-sensitive, so it will work correctly if you name your parameter groupId in the path /group/{groupid}/show for example.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,[AliasAs("sort")]stringsortOrder);GroupList(4,"desc");>>>"/group/4/users?sort=desc"

Dynamic Querystring Parameters

If you specify an object as a query parameter, all public properties which are not null are used as query parameters. Use the Query attribute the change the behavior to 'flatten' your query parameter object. If using this Attribute you can specify values for the Delimiter and the Prefix which are used to 'flatten' the object.

publicclassMyQueryParams{[AliasAs("order")]publicstringSortOrder{get;set;}publicintLimit{get;set;}}[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,MyQueryParamsparams);[Get("/group/{id}/users")]Task<List<User>>GroupListWithAttribute([AliasAs("id")]intgroupId,[Query(".","search")]MyQueryParamsparams);params.SortOrder="desc";params.Limit=10;GroupList(4,params)>>>"/group/4/users?order=desc&Limit=10"
GroupListWithAttribute(4,params)>>>"/group/4/users?search.order=desc&search.Limit=10"

A similar behavior exists if using a Dictionary, but without the advantages of the AliasAs attributes and of course no intellisense and/or type safety.

Collections as Querystring parameters

Use the Query attribute to specify format in which collections should be formatted in query string

[Get("/users/list")]TaskSearch([Query(CollectionFormat.Multi)]int[]ages);Search(new[]{10,20,30})>>>"/users/list?ages=10&ages=20&ages=30"[Get("/users/list")]
Task Search([Query(CollectionFormat.Csv)]int[]ages);
Search(new[]{10,20,30})>>>"/users/list?ages=10%2C20%2C30"

Body content

One of the parameters in your method can be used as the body, by using the Body attribute:

[Post("/users/new")]TaskCreateUser([Body]Useruser);

There are four possibilities for supplying the body data, depending on the type of the parameter:

  • If the type is Stream, the content will be streamed via StreamContent
  • If the type is string, the string will be used directly as the content
  • If the parameter has the attribute [Body(BodySerializationMethod.UrlEncoded)], the content will be URL-encoded (see form posts below)
  • For all other types, the object will be serialized as JSON.

Buffering and the Content-Length header

By default, Refit streams the body content without buffering it. This means you can stream a file from disk, for example, without incurring the overhead of loading the whole file into memory. The downside of this is that no Content-Length header is set on the request. If your API needs you to send a Content-Length header with the request, you can disable this streaming behavior by setting the buffered argument of the [Body] attribute to true:

TaskCreateUser([Body(buffered:true)]Useruser);

JSON content

JSON requests and responses are serialized/deserialized using Json.NET. By default, Refit will use the serializer settings that can be configured by setting Newtonsoft.Json.JsonConvert.DefaultSettings:

JsonConvert.DefaultSettings=()=>newJsonSerializerSettings(){ContractResolver=newCamelCasePropertyNamesContractResolver(),Converters={newStringEnumConverter()}};// Serialized as: {"day":"Saturday"}awaitPostSomeStuff(new{Day=DayOfWeek.Saturday});

As these are global settings they will affect your entire application. It might be beneficial to isolate the settings for calls to a particular API. When creating a Refit generated live interface, you may optionally pass a RefitSettings that will allow you to specify what serializer settings you would like. This allows you to have different serializer settings for separate APIs:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newSnakeCasePropertyNamesContractResolver()}});varotherApi=RestService.For<IOtherApi>("https://api.example.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newCamelCasePropertyNamesContractResolver()}});

Property serialization/deserialization can be customised using Json.NET's JsonProperty attribute:

publicclassFoo{// Works like [AliasAs("b")] would in form posts (see below)[JsonProperty(PropertyName="b")]publicstringBar{get;set;}}

Form posts

For APIs that take form posts (i.e. serialized as application/x-www-form-urlencoded), initialize the Body attribute with BodySerializationMethod.UrlEncoded.

The parameter can be an IDictionary:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Dictionary<string,object>data);}vardata=newDictionary<string,object>{{"v",1},{"tid","UA-1234-5"},{"cid",newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c")},{"t","event"},};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(data);

Or you can just pass any object and all public, readable properties will be serialized as form fields in the request. This approach allows you to alias property names using [AliasAs("whatever")] which can help if the API has cryptic field names:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Measurementmeasurement);}publicclassMeasurement{// Properties can be read-only and [AliasAs] isn't requiredpublicintv{get{return1;}}[AliasAs("tid")]publicstringWebPropertyId{get;set;}[AliasAs("cid")]publicGuidClientId{get;set;}[AliasAs("t")]publicstringType{get;set;}publicobjectIgnoreMe{privateget;set;}}varmeasurement=newMeasurement{WebPropertyId="UA-1234-5",ClientId=newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c"),Type="event"};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(measurement);

If you have a type that has [JsonProperty(PropertyName)] attributes setting property aliases, Refit will use those too ([AliasAs] will take precedence where you have both). This means that the following type will serialize as one=value1&two=value2:

publicclassSomeObject{[JsonProperty(PropertyName="one")]publicstringFirstProperty{get;set;}[JsonProperty(PropertyName="notTwo")][AliasAs("two")]publicstringSecondProperty{get;set;}}

NOTE: This use of AliasAs applies to querystring parameters and form body posts, but not to response objects; for aliasing fields on response objects, you'll still need to use [JsonProperty("full-property-name")].

Setting request headers

Static headers

You can set one or more static request headers for a request applying a Headers attribute to the method:

[Headers("User-Agent: Awesome Octocat App")][Get("/users/{user}")]Task<User>GetUser(stringuser);

Static headers can also be added to every request in the API by applying the Headers attribute to the interface:

[Headers("User-Agent: Awesome Octocat App")]publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser);}

Dynamic headers

If the content of the header needs to be set at runtime, you can add a header with a dynamic value to a request by applying a Header attribute to a parameter:

[Get("/users/{user}")]Task<User>GetUser(stringuser,[Header("Authorization")]stringauthorization);// Will add the header "Authorization: token OAUTH-TOKEN" to the requestvaruser=awaitGetUser("octocat","token OAUTH-TOKEN");

Authorization (Dynamic Headers redux)

The most common reason to use headers is for authorization. Today most API's use some flavor of oAuth with access tokens that expire and refresh tokens that are longer lived.

One way to encapsulate these kinds of token usage, a custom HttpClientHandler can be inserted instead.

For example:

classAuthenticatedHttpClientHandler:HttpClientHandler{privatereadonlyFunc<Task<string>>getToken;publicAuthenticatedHttpClientHandler(Func<Task<string>>getToken){if(getToken==null)thrownewArgumentNullException(nameof(getToken));this.getToken=getToken;}protectedoverrideasyncTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest,CancellationTokencancellationToken){// See if the request has an authorize headervarauth=request.Headers.Authorization;if(auth!=null){vartoken=awaitgetToken().ConfigureAwait(false);request.Headers.Authorization=newAuthenticationHeaderValue(auth.Scheme,token);}returnawaitbase.SendAsync(request,cancellationToken).ConfigureAwait(false);}}

While HttpClient contains a nearly identical method signature, it is used differently. HttpClient.SendAsync is not called by Refit. The HttpClientHandler must be modified instead.

This class is used like so (example uses the ADAL library to manage auto-token refresh but the principal holds for Xamarin.Auth or any other library:

classLoginViewModel{AuthenticationContextcontext=newAuthenticationContext(...);privateasyncTask<string>GetToken(){// The AcquireTokenAsync call will prompt with a UI if necessary// Or otherwise silently use a refresh token to return// a valid access token	vartoken=awaitcontext.AcquireTokenAsync("http://my.service.uri/app","clientId",newUri("callback://complete"));returntoken;}publicasyncvoidLoginAndCallApi(){varapi=RestService.For<IMyRestService>(newHttpClient(newAuthenticatedHttpClientHandler(GetToken)){BaseAddress=newUri("https://the.end.point/")});varlocation=awaitapi.GetLocationOfRebelBase();}}interfaceIMyRestService{[Get("/getPublicInfo")]Task<Foobar>SomePublicMethod();[Get("/secretStuff")][Headers("Authorization: Bearer")]Task<Location>GetLocationOfRebelBase();}

In the above example, any time a method that requires authentication is called, the AuthenticatedHttpClientHandler will try to get a fresh access token. It's up to the app to provide one, checking the expiration time of an existing access token and obtaining a new one if needed.

Redefining headers

Unlike Retrofit, where headers do not overwrite each other and are all added to the request regardless of how many times the same header is defined, Refit takes a similar approach to the approach ASP.NET MVC takes with action filters — redefining a header will replace it, in the following order of precedence:

  • Headers attribute on the interface (lowest priority)
  • Headers attribute on the method
  • Header attribute on a method parameter (highest priority)
[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")]Task<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji: :smile_cat:")]Task<User>GetUser(stringuser);[Post("/users/new")][Headers("X-Emoji: :metal:")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// X-Emoji: :rocket:varusers=awaitGetUsers();// X-Emoji: :smile_cat:varuser=awaitGetUser("octocat");// X-Emoji: :trollface:awaitCreateUser(user,":trollface:");

Removing headers

Headers defined on an interface or method can be removed by redefining a static header without a value (i.e. without : <value>) or passing null for a dynamic header. Empty strings will be included as empty headers.

[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")][Headers("X-Emoji")]// Remove the X-Emoji headerTask<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji:")]// Redefine the X-Emoji header as emptyTask<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// No X-Emoji headervarusers=awaitGetUsers();// X-Emoji: varuser=awaitGetUser("octocat");// No X-Emoji headerawaitCreateUser(user,null);// X-Emoji: awaitCreateUser(user,"");

Multipart uploads

Methods decorated with Multipart attribute will be submitted with multipart content type. At this time, multipart methods support the following parameter types:

  • string (parameter name will be used as name and string value as value)
  • byte array
  • Stream
  • FileInfo

The parameter name will be used as the name of the field in the multipart data. This can be overridden with the AliasAs attribute.

To specify the file name and content type for byte array (byte[]), Stream and FileInfo parameters, use of a wrapper class is required. The wrapper classes for these types are ByteArrayPart, StreamPart and FileInfoPart.

publicinterfaceISomeApi{[Multipart][Post("/users/{id}/photo")]TaskUploadPhoto(intid,[AliasAs("myPhoto")]StreamPartstream);}

To pass a Stream to this method, construct a StreamPart object like so:

someApiInstance.UploadPhoto(id,newStreamPart(myPhotoStream,"photo.jpg","image/jpeg"));

Note: The AttachmentName attribute that was previously described in this section has been deprecated and its use is not recommended.

Retrieving the response

Note that in Refit unlike in Retrofit, there is no option for a synchronous network request - all requests must be async, either via Task or via IObservable. There is also no option to create an async method via a Callback parameter unlike Retrofit, because we live in the async/await future.

Similarly to how body content changes via the parameter type, the return type will determine the content returned.

Returning Task without a type parameter will discard the content and solely tell you whether or not the call succeeded:

[Post("/users/new")]TaskCreateUser([Body]Useruser);// This will throw if the network call failsawaitCreateUser(someUser);

If the type parameter is 'HttpResponseMessage' or 'string', the raw response message or the content as a string will be returned respectively.

// Returns the content as a string (i.e. the JSON data)[Get("/users/{user}")]Task<string>GetUser(stringuser);// Returns the raw response, as an IObservable that can be used with the// Reactive Extensions[Get("/users/{user}")]IObservable<HttpResponseMessage>GetUser(stringuser);

Using generic interfaces

When using something like ASP.NET Web API, it's a fairly common pattern to have a whole stack of CRUD REST services. Refit now supports these, allowing you to define a single API interface with a generic type:

publicinterfaceIReallyExcitingCrudApi<T,inTKey>whereT:class{[Post("")]Task<T>Create([Body]Tpayload);[Get("")]Task<List<T>>ReadAll();[Get("/{key}")]Task<T>ReadOne(TKeykey);[Put("/{key}")]TaskUpdate(TKeykey,[Body]Tpayload);[Delete("/{key}")]TaskDelete(TKeykey);}

Which can be used like this:

// The "/users" part here is kind of important if you want it to work for more // than one type (unless you have a different domain for each type)varapi=RestService.For<IReallyExcitingCrudApi<User,string>>("http://api.example.com/users");

Using HttpClientFactory

Refit has first class support for the ASP.Net Core 2.1 HttpClientFactory. Add a reference to Refit.HttpClientFactory and call the provided extension method in your ConfigureServices method to configure your Refit interface:

services.AddRefitClient<IWebApi>().ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Optionally, a RefitSettings object can be included:

varsettings=newRefitSettings();// Configure refit settings hereservices.AddRefitClient<IWebApi>(settings).ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Note that some of the properties of RefitSettings will be ignored because the HttpClient and HttpClientHandlers will be managed by the HttpClientFactory instead of Refit.

You can then get the api interface using constructor injection:

publicclassHomeController:Controller{publicHomeController(IWebApiwebApi){_webApi=webApi;}privatereadonlyIWebApi_webApi;publicasyncTask<IActionResult>Index(CancellationTokencancellationToken){varthing=await_webApi.GetSomethingWeNeed(cancellationToken);returnView(thing);}}

About

The automatic type-safe REST library for Xamarin and .NET

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Refit: The automatic type-safe REST library for .NET Core, Xamarin and .NET

Refit is a library heavily inspired by Square's Retrofit library, and it turns your REST API into a live interface:

publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);}

The RestService class generates an implementation of IGitHubApi that uses HttpClient to make its calls:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com");varoctocat=awaitgitHubApi.GetUser("octocat");

Where does this work?

Refit currently supports the following platforms and any .NET Standard 1.4 target:

  • UWP
  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS
  • Desktop .NET 4.6.1
  • .NET Core

Note about .NET Core

For .NET Core build-time support, you must use the .NET Core 2 SDK. You can target any supported platform in your library, long as the 2.0+ SDK is used at build-time.

API Attributes

Every method must have an HTTP attribute that provides the request method and relative URL. There are six built-in annotations: Get, Post, Put, Delete, Patch and Head. The relative URL of the resource is specified in the annotation.

[Get("/users/list")]

You can also specify query parameters in the URL:

[Get("/users/list?sort=desc")]

A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }.

If the name of your parameter doesn't match the name in the URL path, use the AliasAs attribute.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId);

Parameters that are not specified as a URL substitution will automatically be used as query parameters. This is different than Retrofit, where all parameters must be explicitly specified.

The comparison between parameter name and URL parameter is not case-sensitive, so it will work correctly if you name your parameter groupId in the path /group/{groupid}/show for example.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,[AliasAs("sort")]stringsortOrder);GroupList(4,"desc");>>>"/group/4/users?sort=desc"

Dynamic Querystring Parameters

If you specify an object as a query parameter, all public properties which are not null are used as query parameters. Use the Query attribute the change the behavior to 'flatten' your query parameter object. If using this Attribute you can specify values for the Delimiter and the Prefix which are used to 'flatten' the object.

publicclassMyQueryParams{[AliasAs("order")]publicstringSortOrder{get;set;}publicintLimit{get;set;}}[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,MyQueryParamsparams);[Get("/group/{id}/users")]Task<List<User>>GroupListWithAttribute([AliasAs("id")]intgroupId,[Query(".","search")]MyQueryParamsparams);params.SortOrder="desc";params.Limit=10;GroupList(4,params)>>>"/group/4/users?order=desc&Limit=10"
GroupListWithAttribute(4,params)>>>"/group/4/users?search.order=desc&search.Limit=10"

A similar behavior exists if using a Dictionary, but without the advantages of the AliasAs attributes and of course no intellisense and/or type safety.

Collections as Querystring parameters

Use the Query attribute to specify format in which collections should be formatted in query string

[Get("/users/list")]TaskSearch([Query(CollectionFormat.Multi)]int[]ages);Search(new[]{10,20,30})>>>"/users/list?ages=10&ages=20&ages=30"[Get("/users/list")]
Task Search([Query(CollectionFormat.Csv)]int[]ages);
Search(new[]{10,20,30})>>>"/users/list?ages=10%2C20%2C30"

Body content

One of the parameters in your method can be used as the body, by using the Body attribute:

[Post("/users/new")]TaskCreateUser([Body]Useruser);

There are four possibilities for supplying the body data, depending on the type of the parameter:

  • If the type is Stream, the content will be streamed via StreamContent
  • If the type is string, the string will be used directly as the content
  • If the parameter has the attribute [Body(BodySerializationMethod.UrlEncoded)], the content will be URL-encoded (see form posts below)
  • For all other types, the object will be serialized as JSON.

Buffering and the Content-Length header

By default, Refit streams the body content without buffering it. This means you can stream a file from disk, for example, without incurring the overhead of loading the whole file into memory. The downside of this is that no Content-Length header is set on the request. If your API needs you to send a Content-Length header with the request, you can disable this streaming behavior by setting the buffered argument of the [Body] attribute to true:

TaskCreateUser([Body(buffered:true)]Useruser);

JSON content

JSON requests and responses are serialized/deserialized using Json.NET. By default, Refit will use the serializer settings that can be configured by setting Newtonsoft.Json.JsonConvert.DefaultSettings:

JsonConvert.DefaultSettings=()=>newJsonSerializerSettings(){ContractResolver=newCamelCasePropertyNamesContractResolver(),Converters={newStringEnumConverter()}};// Serialized as: {"day":"Saturday"}awaitPostSomeStuff(new{Day=DayOfWeek.Saturday});

As these are global settings they will affect your entire application. It might be beneficial to isolate the settings for calls to a particular API. When creating a Refit generated live interface, you may optionally pass a RefitSettings that will allow you to specify what serializer settings you would like. This allows you to have different serializer settings for separate APIs:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newSnakeCasePropertyNamesContractResolver()}});varotherApi=RestService.For<IOtherApi>("https://api.example.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newCamelCasePropertyNamesContractResolver()}});

Property serialization/deserialization can be customised using Json.NET's JsonProperty attribute:

publicclassFoo{// Works like [AliasAs("b")] would in form posts (see below)[JsonProperty(PropertyName="b")]publicstringBar{get;set;}}

Form posts

For APIs that take form posts (i.e. serialized as application/x-www-form-urlencoded), initialize the Body attribute with BodySerializationMethod.UrlEncoded.

The parameter can be an IDictionary:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Dictionary<string,object>data);}vardata=newDictionary<string,object>{{"v",1},{"tid","UA-1234-5"},{"cid",newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c")},{"t","event"},};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(data);

Or you can just pass any object and all public, readable properties will be serialized as form fields in the request. This approach allows you to alias property names using [AliasAs("whatever")] which can help if the API has cryptic field names:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Measurementmeasurement);}publicclassMeasurement{// Properties can be read-only and [AliasAs] isn't requiredpublicintv{get{return1;}}[AliasAs("tid")]publicstringWebPropertyId{get;set;}[AliasAs("cid")]publicGuidClientId{get;set;}[AliasAs("t")]publicstringType{get;set;}publicobjectIgnoreMe{privateget;set;}}varmeasurement=newMeasurement{WebPropertyId="UA-1234-5",ClientId=newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c"),Type="event"};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(measurement);

If you have a type that has [JsonProperty(PropertyName)] attributes setting property aliases, Refit will use those too ([AliasAs] will take precedence where you have both). This means that the following type will serialize as one=value1&two=value2:

publicclassSomeObject{[JsonProperty(PropertyName="one")]publicstringFirstProperty{get;set;}[JsonProperty(PropertyName="notTwo")][AliasAs("two")]publicstringSecondProperty{get;set;}}

NOTE: This use of AliasAs applies to querystring parameters and form body posts, but not to response objects; for aliasing fields on response objects, you'll still need to use [JsonProperty("full-property-name")].

Setting request headers

Static headers

You can set one or more static request headers for a request applying a Headers attribute to the method:

[Headers("User-Agent: Awesome Octocat App")][Get("/users/{user}")]Task<User>GetUser(stringuser);

Static headers can also be added to every request in the API by applying the Headers attribute to the interface:

[Headers("User-Agent: Awesome Octocat App")]publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser);}

Dynamic headers

If the content of the header needs to be set at runtime, you can add a header with a dynamic value to a request by applying a Header attribute to a parameter:

[Get("/users/{user}")]Task<User>GetUser(stringuser,[Header("Authorization")]stringauthorization);// Will add the header "Authorization: token OAUTH-TOKEN" to the requestvaruser=awaitGetUser("octocat","token OAUTH-TOKEN");

Authorization (Dynamic Headers redux)

The most common reason to use headers is for authorization. Today most API's use some flavor of oAuth with access tokens that expire and refresh tokens that are longer lived.

One way to encapsulate these kinds of token usage, a custom HttpClientHandler can be inserted instead.

For example:

classAuthenticatedHttpClientHandler:HttpClientHandler{privatereadonlyFunc<Task<string>>getToken;publicAuthenticatedHttpClientHandler(Func<Task<string>>getToken){if(getToken==null)thrownewArgumentNullException(nameof(getToken));this.getToken=getToken;}protectedoverrideasyncTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest,CancellationTokencancellationToken){// See if the request has an authorize headervarauth=request.Headers.Authorization;if(auth!=null){vartoken=awaitgetToken().ConfigureAwait(false);request.Headers.Authorization=newAuthenticationHeaderValue(auth.Scheme,token);}returnawaitbase.SendAsync(request,cancellationToken).ConfigureAwait(false);}}

While HttpClient contains a nearly identical method signature, it is used differently. HttpClient.SendAsync is not called by Refit. The HttpClientHandler must be modified instead.

This class is used like so (example uses the ADAL library to manage auto-token refresh but the principal holds for Xamarin.Auth or any other library:

classLoginViewModel{AuthenticationContextcontext=newAuthenticationContext(...);privateasyncTask<string>GetToken(){// The AcquireTokenAsync call will prompt with a UI if necessary// Or otherwise silently use a refresh token to return// a valid access token	vartoken=awaitcontext.AcquireTokenAsync("http://my.service.uri/app","clientId",newUri("callback://complete"));returntoken;}publicasyncvoidLoginAndCallApi(){varapi=RestService.For<IMyRestService>(newHttpClient(newAuthenticatedHttpClientHandler(GetToken)){BaseAddress=newUri("https://the.end.point/")});varlocation=awaitapi.GetLocationOfRebelBase();}}interfaceIMyRestService{[Get("/getPublicInfo")]Task<Foobar>SomePublicMethod();[Get("/secretStuff")][Headers("Authorization: Bearer")]Task<Location>GetLocationOfRebelBase();}

In the above example, any time a method that requires authentication is called, the AuthenticatedHttpClientHandler will try to get a fresh access token. It's up to the app to provide one, checking the expiration time of an existing access token and obtaining a new one if needed.

Redefining headers

Unlike Retrofit, where headers do not overwrite each other and are all added to the request regardless of how many times the same header is defined, Refit takes a similar approach to the approach ASP.NET MVC takes with action filters — redefining a header will replace it, in the following order of precedence:

  • Headers attribute on the interface (lowest priority)
  • Headers attribute on the method
  • Header attribute on a method parameter (highest priority)
[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")]Task<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji: :smile_cat:")]Task<User>GetUser(stringuser);[Post("/users/new")][Headers("X-Emoji: :metal:")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// X-Emoji: :rocket:varusers=awaitGetUsers();// X-Emoji: :smile_cat:varuser=awaitGetUser("octocat");// X-Emoji: :trollface:awaitCreateUser(user,":trollface:");

Removing headers

Headers defined on an interface or method can be removed by redefining a static header without a value (i.e. without : <value>) or passing null for a dynamic header. Empty strings will be included as empty headers.

[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")][Headers("X-Emoji")]// Remove the X-Emoji headerTask<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji:")]// Redefine the X-Emoji header as emptyTask<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// No X-Emoji headervarusers=awaitGetUsers();// X-Emoji: varuser=awaitGetUser("octocat");// No X-Emoji headerawaitCreateUser(user,null);// X-Emoji: awaitCreateUser(user,"");

Multipart uploads

Methods decorated with Multipart attribute will be submitted with multipart content type. At this time, multipart methods support the following parameter types:

  • string (parameter name will be used as name and string value as value)
  • byte array
  • Stream
  • FileInfo

The parameter name will be used as the name of the field in the multipart data. This can be overridden with the AliasAs attribute.

To specify the file name and content type for byte array (byte[]), Stream and FileInfo parameters, use of a wrapper class is required. The wrapper classes for these types are ByteArrayPart, StreamPart and FileInfoPart.

publicinterfaceISomeApi{[Multipart][Post("/users/{id}/photo")]TaskUploadPhoto(intid,[AliasAs("myPhoto")]StreamPartstream);}

To pass a Stream to this method, construct a StreamPart object like so:

someApiInstance.UploadPhoto(id,newStreamPart(myPhotoStream,"photo.jpg","image/jpeg"));

Note: The AttachmentName attribute that was previously described in this section has been deprecated and its use is not recommended.

Retrieving the response

Note that in Refit unlike in Retrofit, there is no option for a synchronous network request - all requests must be async, either via Task or via IObservable. There is also no option to create an async method via a Callback parameter unlike Retrofit, because we live in the async/await future.

Similarly to how body content changes via the parameter type, the return type will determine the content returned.

Returning Task without a type parameter will discard the content and solely tell you whether or not the call succeeded:

[Post("/users/new")]TaskCreateUser([Body]Useruser);// This will throw if the network call failsawaitCreateUser(someUser);

If the type parameter is 'HttpResponseMessage' or 'string', the raw response message or the content as a string will be returned respectively.

// Returns the content as a string (i.e. the JSON data)[Get("/users/{user}")]Task<string>GetUser(stringuser);// Returns the raw response, as an IObservable that can be used with the// Reactive Extensions[Get("/users/{user}")]IObservable<HttpResponseMessage>GetUser(stringuser);

Using generic interfaces

When using something like ASP.NET Web API, it's a fairly common pattern to have a whole stack of CRUD REST services. Refit now supports these, allowing you to define a single API interface with a generic type:

publicinterfaceIReallyExcitingCrudApi<T,inTKey>whereT:class{[Post("")]Task<T>Create([Body]Tpayload);[Get("")]Task<List<T>>ReadAll();[Get("/{key}")]Task<T>ReadOne(TKeykey);[Put("/{key}")]TaskUpdate(TKeykey,[Body]Tpayload);[Delete("/{key}")]TaskDelete(TKeykey);}

Which can be used like this:

// The "/users" part here is kind of important if you want it to work for more // than one type (unless you have a different domain for each type)varapi=RestService.For<IReallyExcitingCrudApi<User,string>>("http://api.example.com/users");

Using HttpClientFactory

Refit has first class support for the ASP.Net Core 2.1 HttpClientFactory. Add a reference to Refit.HttpClientFactory and call the provided extension method in your ConfigureServices method to configure your Refit interface:

services.AddRefitClient<IWebApi>().ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Optionally, a RefitSettings object can be included:

varsettings=newRefitSettings();// Configure refit settings hereservices.AddRefitClient<IWebApi>(settings).ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Note that some of the properties of RefitSettings will be ignored because the HttpClient and HttpClientHandlers will be managed by the HttpClientFactory instead of Refit.

You can then get the api interface using constructor injection:

publicclassHomeController:Controller{publicHomeController(IWebApiwebApi){_webApi=webApi;}privatereadonlyIWebApi_webApi;publicasyncTask<IActionResult>Index(CancellationTokencancellationToken){varthing=await_webApi.GetSomethingWeNeed(cancellationToken);returnView(thing);}}

About

The automatic type-safe REST library for Xamarin and .NET

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Refit: The automatic type-safe REST library for .NET Core, Xamarin and .NET

Refit is a library heavily inspired by Square's Retrofit library, and it turns your REST API into a live interface:

publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);}

The RestService class generates an implementation of IGitHubApi that uses HttpClient to make its calls:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com");varoctocat=awaitgitHubApi.GetUser("octocat");

Where does this work?

Refit currently supports the following platforms and any .NET Standard 1.4 target:

  • UWP
  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS
  • Desktop .NET 4.6.1
  • .NET Core

Note about .NET Core

For .NET Core build-time support, you must use the .NET Core 2 SDK. You can target any supported platform in your library, long as the 2.0+ SDK is used at build-time.

API Attributes

Every method must have an HTTP attribute that provides the request method and relative URL. There are six built-in annotations: Get, Post, Put, Delete, Patch and Head. The relative URL of the resource is specified in the annotation.

[Get("/users/list")]

You can also specify query parameters in the URL:

[Get("/users/list?sort=desc")]

A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }.

If the name of your parameter doesn't match the name in the URL path, use the AliasAs attribute.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId);

Parameters that are not specified as a URL substitution will automatically be used as query parameters. This is different than Retrofit, where all parameters must be explicitly specified.

The comparison between parameter name and URL parameter is not case-sensitive, so it will work correctly if you name your parameter groupId in the path /group/{groupid}/show for example.

[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,[AliasAs("sort")]stringsortOrder);GroupList(4,"desc");>>>"/group/4/users?sort=desc"

Dynamic Querystring Parameters

If you specify an object as a query parameter, all public properties which are not null are used as query parameters. Use the Query attribute the change the behavior to 'flatten' your query parameter object. If using this Attribute you can specify values for the Delimiter and the Prefix which are used to 'flatten' the object.

publicclassMyQueryParams{[AliasAs("order")]publicstringSortOrder{get;set;}publicintLimit{get;set;}}[Get("/group/{id}/users")]Task<List<User>>GroupList([AliasAs("id")]intgroupId,MyQueryParamsparams);[Get("/group/{id}/users")]Task<List<User>>GroupListWithAttribute([AliasAs("id")]intgroupId,[Query(".","search")]MyQueryParamsparams);params.SortOrder="desc";params.Limit=10;GroupList(4,params)>>>"/group/4/users?order=desc&Limit=10"
GroupListWithAttribute(4,params)>>>"/group/4/users?search.order=desc&search.Limit=10"

A similar behavior exists if using a Dictionary, but without the advantages of the AliasAs attributes and of course no intellisense and/or type safety.

Collections as Querystring parameters

Use the Query attribute to specify format in which collections should be formatted in query string

[Get("/users/list")]TaskSearch([Query(CollectionFormat.Multi)]int[]ages);Search(new[]{10,20,30})>>>"/users/list?ages=10&ages=20&ages=30"[Get("/users/list")]
Task Search([Query(CollectionFormat.Csv)]int[]ages);
Search(new[]{10,20,30})>>>"/users/list?ages=10%2C20%2C30"

Body content

One of the parameters in your method can be used as the body, by using the Body attribute:

[Post("/users/new")]TaskCreateUser([Body]Useruser);

There are four possibilities for supplying the body data, depending on the type of the parameter:

  • If the type is Stream, the content will be streamed via StreamContent
  • If the type is string, the string will be used directly as the content
  • If the parameter has the attribute [Body(BodySerializationMethod.UrlEncoded)], the content will be URL-encoded (see form posts below)
  • For all other types, the object will be serialized as JSON.

Buffering and the Content-Length header

By default, Refit streams the body content without buffering it. This means you can stream a file from disk, for example, without incurring the overhead of loading the whole file into memory. The downside of this is that no Content-Length header is set on the request. If your API needs you to send a Content-Length header with the request, you can disable this streaming behavior by setting the buffered argument of the [Body] attribute to true:

TaskCreateUser([Body(buffered:true)]Useruser);

JSON content

JSON requests and responses are serialized/deserialized using Json.NET. By default, Refit will use the serializer settings that can be configured by setting Newtonsoft.Json.JsonConvert.DefaultSettings:

JsonConvert.DefaultSettings=()=>newJsonSerializerSettings(){ContractResolver=newCamelCasePropertyNamesContractResolver(),Converters={newStringEnumConverter()}};// Serialized as: {"day":"Saturday"}awaitPostSomeStuff(new{Day=DayOfWeek.Saturday});

As these are global settings they will affect your entire application. It might be beneficial to isolate the settings for calls to a particular API. When creating a Refit generated live interface, you may optionally pass a RefitSettings that will allow you to specify what serializer settings you would like. This allows you to have different serializer settings for separate APIs:

vargitHubApi=RestService.For<IGitHubApi>("https://api.github.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newSnakeCasePropertyNamesContractResolver()}});varotherApi=RestService.For<IOtherApi>("https://api.example.com",newRefitSettings{JsonSerializerSettings=newJsonSerializerSettings{ContractResolver=newCamelCasePropertyNamesContractResolver()}});

Property serialization/deserialization can be customised using Json.NET's JsonProperty attribute:

publicclassFoo{// Works like [AliasAs("b")] would in form posts (see below)[JsonProperty(PropertyName="b")]publicstringBar{get;set;}}

Form posts

For APIs that take form posts (i.e. serialized as application/x-www-form-urlencoded), initialize the Body attribute with BodySerializationMethod.UrlEncoded.

The parameter can be an IDictionary:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Dictionary<string,object>data);}vardata=newDictionary<string,object>{{"v",1},{"tid","UA-1234-5"},{"cid",newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c")},{"t","event"},};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(data);

Or you can just pass any object and all public, readable properties will be serialized as form fields in the request. This approach allows you to alias property names using [AliasAs("whatever")] which can help if the API has cryptic field names:

publicinterfaceIMeasurementProtocolApi{[Post("/collect")]TaskCollect([Body(BodySerializationMethod.UrlEncoded)]Measurementmeasurement);}publicclassMeasurement{// Properties can be read-only and [AliasAs] isn't requiredpublicintv{get{return1;}}[AliasAs("tid")]publicstringWebPropertyId{get;set;}[AliasAs("cid")]publicGuidClientId{get;set;}[AliasAs("t")]publicstringType{get;set;}publicobjectIgnoreMe{privateget;set;}}varmeasurement=newMeasurement{WebPropertyId="UA-1234-5",ClientId=newGuid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c"),Type="event"};// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=eventawaitapi.Collect(measurement);

If you have a type that has [JsonProperty(PropertyName)] attributes setting property aliases, Refit will use those too ([AliasAs] will take precedence where you have both). This means that the following type will serialize as one=value1&two=value2:

publicclassSomeObject{[JsonProperty(PropertyName="one")]publicstringFirstProperty{get;set;}[JsonProperty(PropertyName="notTwo")][AliasAs("two")]publicstringSecondProperty{get;set;}}

NOTE: This use of AliasAs applies to querystring parameters and form body posts, but not to response objects; for aliasing fields on response objects, you'll still need to use [JsonProperty("full-property-name")].

Setting request headers

Static headers

You can set one or more static request headers for a request applying a Headers attribute to the method:

[Headers("User-Agent: Awesome Octocat App")][Get("/users/{user}")]Task<User>GetUser(stringuser);

Static headers can also be added to every request in the API by applying the Headers attribute to the interface:

[Headers("User-Agent: Awesome Octocat App")]publicinterfaceIGitHubApi{[Get("/users/{user}")]Task<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser);}

Dynamic headers

If the content of the header needs to be set at runtime, you can add a header with a dynamic value to a request by applying a Header attribute to a parameter:

[Get("/users/{user}")]Task<User>GetUser(stringuser,[Header("Authorization")]stringauthorization);// Will add the header "Authorization: token OAUTH-TOKEN" to the requestvaruser=awaitGetUser("octocat","token OAUTH-TOKEN");

Authorization (Dynamic Headers redux)

The most common reason to use headers is for authorization. Today most API's use some flavor of oAuth with access tokens that expire and refresh tokens that are longer lived.

One way to encapsulate these kinds of token usage, a custom HttpClientHandler can be inserted instead.

For example:

classAuthenticatedHttpClientHandler:HttpClientHandler{privatereadonlyFunc<Task<string>>getToken;publicAuthenticatedHttpClientHandler(Func<Task<string>>getToken){if(getToken==null)thrownewArgumentNullException(nameof(getToken));this.getToken=getToken;}protectedoverrideasyncTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest,CancellationTokencancellationToken){// See if the request has an authorize headervarauth=request.Headers.Authorization;if(auth!=null){vartoken=awaitgetToken().ConfigureAwait(false);request.Headers.Authorization=newAuthenticationHeaderValue(auth.Scheme,token);}returnawaitbase.SendAsync(request,cancellationToken).ConfigureAwait(false);}}

While HttpClient contains a nearly identical method signature, it is used differently. HttpClient.SendAsync is not called by Refit. The HttpClientHandler must be modified instead.

This class is used like so (example uses the ADAL library to manage auto-token refresh but the principal holds for Xamarin.Auth or any other library:

classLoginViewModel{AuthenticationContextcontext=newAuthenticationContext(...);privateasyncTask<string>GetToken(){// The AcquireTokenAsync call will prompt with a UI if necessary// Or otherwise silently use a refresh token to return// a valid access token	vartoken=awaitcontext.AcquireTokenAsync("http://my.service.uri/app","clientId",newUri("callback://complete"));returntoken;}publicasyncvoidLoginAndCallApi(){varapi=RestService.For<IMyRestService>(newHttpClient(newAuthenticatedHttpClientHandler(GetToken)){BaseAddress=newUri("https://the.end.point/")});varlocation=awaitapi.GetLocationOfRebelBase();}}interfaceIMyRestService{[Get("/getPublicInfo")]Task<Foobar>SomePublicMethod();[Get("/secretStuff")][Headers("Authorization: Bearer")]Task<Location>GetLocationOfRebelBase();}

In the above example, any time a method that requires authentication is called, the AuthenticatedHttpClientHandler will try to get a fresh access token. It's up to the app to provide one, checking the expiration time of an existing access token and obtaining a new one if needed.

Redefining headers

Unlike Retrofit, where headers do not overwrite each other and are all added to the request regardless of how many times the same header is defined, Refit takes a similar approach to the approach ASP.NET MVC takes with action filters — redefining a header will replace it, in the following order of precedence:

  • Headers attribute on the interface (lowest priority)
  • Headers attribute on the method
  • Header attribute on a method parameter (highest priority)
[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")]Task<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji: :smile_cat:")]Task<User>GetUser(stringuser);[Post("/users/new")][Headers("X-Emoji: :metal:")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// X-Emoji: :rocket:varusers=awaitGetUsers();// X-Emoji: :smile_cat:varuser=awaitGetUser("octocat");// X-Emoji: :trollface:awaitCreateUser(user,":trollface:");

Removing headers

Headers defined on an interface or method can be removed by redefining a static header without a value (i.e. without : <value>) or passing null for a dynamic header. Empty strings will be included as empty headers.

[Headers("X-Emoji: :rocket:")]publicinterfaceIGitHubApi{[Get("/users/list")][Headers("X-Emoji")]// Remove the X-Emoji headerTask<List>GetUsers();[Get("/users/{user}")][Headers("X-Emoji:")]// Redefine the X-Emoji header as emptyTask<User>GetUser(stringuser);[Post("/users/new")]TaskCreateUser([Body]Useruser,[Header("X-Emoji")]stringemoji);}// No X-Emoji headervarusers=awaitGetUsers();// X-Emoji: varuser=awaitGetUser("octocat");// No X-Emoji headerawaitCreateUser(user,null);// X-Emoji: awaitCreateUser(user,"");

Multipart uploads

Methods decorated with Multipart attribute will be submitted with multipart content type. At this time, multipart methods support the following parameter types:

  • string (parameter name will be used as name and string value as value)
  • byte array
  • Stream
  • FileInfo

The parameter name will be used as the name of the field in the multipart data. This can be overridden with the AliasAs attribute.

To specify the file name and content type for byte array (byte[]), Stream and FileInfo parameters, use of a wrapper class is required. The wrapper classes for these types are ByteArrayPart, StreamPart and FileInfoPart.

publicinterfaceISomeApi{[Multipart][Post("/users/{id}/photo")]TaskUploadPhoto(intid,[AliasAs("myPhoto")]StreamPartstream);}

To pass a Stream to this method, construct a StreamPart object like so:

someApiInstance.UploadPhoto(id,newStreamPart(myPhotoStream,"photo.jpg","image/jpeg"));

Note: The AttachmentName attribute that was previously described in this section has been deprecated and its use is not recommended.

Retrieving the response

Note that in Refit unlike in Retrofit, there is no option for a synchronous network request - all requests must be async, either via Task or via IObservable. There is also no option to create an async method via a Callback parameter unlike Retrofit, because we live in the async/await future.

Similarly to how body content changes via the parameter type, the return type will determine the content returned.

Returning Task without a type parameter will discard the content and solely tell you whether or not the call succeeded:

[Post("/users/new")]TaskCreateUser([Body]Useruser);// This will throw if the network call failsawaitCreateUser(someUser);

If the type parameter is 'HttpResponseMessage' or 'string', the raw response message or the content as a string will be returned respectively.

// Returns the content as a string (i.e. the JSON data)[Get("/users/{user}")]Task<string>GetUser(stringuser);// Returns the raw response, as an IObservable that can be used with the// Reactive Extensions[Get("/users/{user}")]IObservable<HttpResponseMessage>GetUser(stringuser);

Using generic interfaces

When using something like ASP.NET Web API, it's a fairly common pattern to have a whole stack of CRUD REST services. Refit now supports these, allowing you to define a single API interface with a generic type:

publicinterfaceIReallyExcitingCrudApi<T,inTKey>whereT:class{[Post("")]Task<T>Create([Body]Tpayload);[Get("")]Task<List<T>>ReadAll();[Get("/{key}")]Task<T>ReadOne(TKeykey);[Put("/{key}")]TaskUpdate(TKeykey,[Body]Tpayload);[Delete("/{key}")]TaskDelete(TKeykey);}

Which can be used like this:

// The "/users" part here is kind of important if you want it to work for more // than one type (unless you have a different domain for each type)varapi=RestService.For<IReallyExcitingCrudApi<User,string>>("http://api.example.com/users");

Using HttpClientFactory

Refit has first class support for the ASP.Net Core 2.1 HttpClientFactory. Add a reference to Refit.HttpClientFactory and call the provided extension method in your ConfigureServices method to configure your Refit interface:

services.AddRefitClient<IWebApi>().ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Optionally, a RefitSettings object can be included:

varsettings=newRefitSettings();// Configure refit settings hereservices.AddRefitClient<IWebApi>(settings).ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

Note that some of the properties of RefitSettings will be ignored because the HttpClient and HttpClientHandlers will be managed by the HttpClientFactory instead of Refit.

You can then get the api interface using constructor injection:

publicclassHomeController:Controller{publicHomeController(IWebApiwebApi){_webApi=webApi;}privatereadonlyIWebApi_webApi;publicasyncTask<IActionResult>Index(CancellationTokencancellationToken){varthing=await_webApi.GetSomethingWeNeed(cancellationToken);returnView(thing);}}

About

The automatic type-safe REST library for Xamarin and .NET

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages