Rest.Fody
Rest.Fody copied to clipboard
A multi-platform REST client powered by Fody, and heavily inspired by paulcbetts's Refit and canton7's RestEase.
Rest.Fody
A Fody addin, heavily inspired by Refit and RestEase.
Thankfully, the source code for ReactiveUI.Fody was easy to understand, and greatly helped me.
Attention
This project is no longer maintained, and is not expected to work.
Basic syntax
[ServiceFor("http://example.com/api/v2")]
public class API
{
[Get("/user")]
public extern async Task<MyUser> GetUser();
}
Source structure
Rest.Fody
All the weaving code.
Rest.Fody.Portable
All the attributes.
Rest.Fody.Tests
Some tests, with Shouldly.
Shared
Code shared between Rest.Fody.Portable and Rest.Fody.
API
Basic anatomy of a class
[ServiceFor("http://example.com/api/v1")]
public class API
{
public API()
{
}
[Get("/user")]
public extern Task<User> GetUser();
}
A class will be recognized and modified by Rest.Fody if either:
- It has the attribute
[ServiceFor(URL)]
; - It has the attribute
[Service]
, and contains a non-virtual HttpClient marked with the attribute[RestClient]
.
In the first case, a private HttpClient
field will be created, and be used internally.
With the [ServiceFor(URL)]
attribute, it is also possible to specify custom headers, via the
[Header(name, value)]
attribute.
Making requests
[Get("/")]
public extern Task CheckInternetConnection();
A request must be marked extern
, and return either a Task
, a Task<T>
, or a IObservable<T>
(in which case System.Reactive.Linq
must be referenced).
On failure, a request will throw a RestException
, which contains a HttpResponseMessage
.
Deserialization / Serialization
To free itself of any dependency besides Fody and Mono.Cecil, Rest.Fody will not be able to deserialize or serialize types besides all numeric types, Stream
, string
and byte[]
. When deserializing, HttpResponseMessage
is also accepted.
To add your own (de)serialization, declare one of those methods with the [RestSerialize]
or [RestDeserialize]
attribute:
-
string Serialize(object obj)
orbyte[] Serialize(object obj)
-
T Deserialize<T>(string src)
orT Deserialize<T>(byte[] bytes)
Valid implementations:
[Service]
public class API
{
[RestDeserializer] private T Deserialize<T>(string str) { ... }
[RestDeserializer] private T Deserialize<T>(byte[] buf) { ... }
[RestSerializer] private string Serialize(object o) { ... }
[RestSerializer] private byte[] Serialize(object o) { ... }
}
or
public class Utils
{
[RestDeserializer] public static T Deserialize<T>(string str) { ... }
[RestDeserializer] public static T Deserialize<T>(byte[] buf) { ... }
[RestSerializer] public static string Serialize(object o) { ... }
[RestSerializer] public static byte[] Serialize(object o) { ... }
}
The instance methods will be chosen in priority, but will fallback to the static methods if needed.
Query, dynamic url
[Get("/todo")]
public extern Task<List<Todo>> GetTodos([Query] int offset, [Query] int count);
[Post("/todo/{todoId}")]
public extern Task<Todo> SaveTodo(string todoId);
[Delete("/todo/{todoId}")]
public extern Task<Todo> DeleteTodo([Alias("todoId")] string id, [Query] string @if);
Four ways to specify query parameters:
-
[Query] object obj
or[Query(name)] object obj
-
[Query] IDictionary<string, object> query
or[Query(name)] IDictionary<string, object> query
If the name of the query starts with a '@', it will be removed.
Two ways to change a dynamic url:
-
string id
-
[Alias(name)] string id
Body
[Put("/todo/{todoId}")]
public extern Task<Todo> UpdateTodo(string todoId, [Body] Todo todo);
The body of the request must be unique, and marked with the [Body]
attribute.
Headers
[ServiceFor("http://example.com/api/v1")]
[Header("Authorization", "Bearer xxx")]
public class API
{
[Header("Authorization")]
public extern Task DoSomethingWithoutAuthenticating([Header("X-Client")] string client);
}
Headers can be specified on both classes, methods and parameters:
- On classes,
[Header(name)]
will throw, and[Header(name, value)]
will add a default header. - On methods,
[Header(name)]
will throw, and[Header(name, value)]
will override or add a new header. - On parameters,
[Header(name)]
will override or add a new header, and[Header(name, value)]
will throw.
Note: Default headers specified on a class will be ignored if the class provides its own HttpClient
.
Note: A [Headers]
attribute is valid on parameters that implements IDictionary<string, string>
.
Options
In FodyWeaver.xml
, options can be passed to Rest.Fody via XML (sorry, those options have very long name).
-
AddHeadersToAlreadyExistingHttpClient (Default:
False
): Add the headers provided by[Header] class
toHttpClient
, even if it is provided by the user. -
ThrowRestExceptionOnInternetError (Default:
False
): On internet error (can't connect, timeout, ...), throwRestException
instead ofHttpRequestException
.RestException.IsError
will be set to true, and onlyResponseMessage
will benull
.
Misc
- Yes, you can do
[Get("http://full.url/user")]
. - You can add your own attributes if they inherit the
HttpMethodAttribute
and override its static propertyMethod
with thenew
keyword. - Most checks are done on build, meaning most runtime exceptions will be avoided. But that does not mean that it is perfectly safe.
- There is no need to create a
Serialize(object)
method if we only manipulate byte arrays and strings. Same remark withT Deserialize<T>()
. - If you use
IObservable<T>
, make sureSystem.Reactive.Linq
is referenced, and used. If you never use it in your project, it will unlikely be referenced during build. - By default,
CancellationToken.None
will be used ; you can however pass your ownCancellationToken
to any of your requests.