Trying to create something really lightweight. Sources are on GitHub.
To create a proxy we need to define an interface first, e.g.:
// Fake Online REST API for Testing and Prototyping
[Site("https://jsonplaceholder.typicode.com")]
public interface ITypicode
{
[Get("posts")]
Task<BlogPost[]> GetAsync();
[Get("posts/{0}")]
Task<BlogPost> GetAsync(int id);
[Post("posts")]
Task<BlogPost> PostAsync([Body] BlogPost data);
[Put("posts/{0}")]
Task<BlogPost> PutAsync(int id, [Body] BlogPost data);
[Delete("posts/{0}")]
Task<BlogPost> DeleteAsync(int id);
}
public class BlogPost
{
public int UserId { get; set; }
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
}
At this moment we actually know enough to generate a proxy:
ITypicode typicode = RestClient.Create<ITypicode>();
BlogPost blogPost = await typicode.PutAsync(1, new BlogPost { Body = "Wow!" });
Console.WriteLine(blogPost.Body);
We can inject HttpMessageHandler:
ITypicode typicode = RestClient.Create<ITypicode>(handler);
We can also emit the proxy class for dependency injection:
Type typicodeType = RestClient.Emit<ITypicode>();
About error handling – this exception is thrown for unsuccessful HTTP status codes:
public class RestException : Exception
{
public RestException(HttpResponseMessage response)
{
Response = response;
}
public HttpResponseMessage Response { get; }
public override string ToString() =>
Response.Content.ReadAsStringAsync().Result;
}
We could specify extra type parameter for the SiteAttribute
:
[Site("https://jsonplaceholder.typicode.com", Error = typeof(TypicodeError))]
public interface ITypicode
{
// …
}
to have generic exception be thrown instead:
public class RestException<TError> : RestException
{
public RestException(HttpResponseMessage response)
: base(response)
{
}
public T Error => JsonConvert.DeserializeObject<TError>(ToString());
}
So error response body will be kindly deserialized for us.
It is possible to implement API interface on a server side ASP.NET Web API Controller also to insure compatibility.
What do you think about this design?
To be continued with implementation details.
UPDATE: Adding support for HTTP headers – something like this:
[Site("https://jsonplaceholder.typicode.com")]
public interface ITypicode
{
[Get("posts/{0}")]
[Header("X-API-KEY: {1}")] // req - in
[Header("Content-Type: {2}; charset={3}")] // res - out
Task<BlogPost> GetAsync(
int id, string apiKey, out string contentType, out string charset);
}
Does it look good? Anything else that might be useful?