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?
-
\$\begingroup\$ P.S. I started it yesterday, so it is definitely rough ;) \$\endgroup\$Dmitry Nogin– Dmitry Nogin2016年09月04日 17:24:28 +00:00Commented Sep 4, 2016 at 17:24
1 Answer 1
Usually when I see your questions there isn't much for me to say, because you usually flesh everything out really well. :) (Probably why this has been unanswered so long.)
That said, I think I do have one comment here:
If you have support to edit the HeaderAttribute
or the GetAttribute
, I would consider replacing the {0}
, {1}
(etc.) format symbols with a named format symbol.
[Get("posts/{id}")]
Task<BlogPost> GetAsync(int id);
[Site("https://jsonplaceholder.typicode.com")]
public interface ITypicode
{
[Get("posts/{id}")]
[Header("X-API-KEY: {apiKey}")] // req - in
[Header("Content-Type: {contentType}; charset={charset}")] // res - out
Task<BlogPost> GetAsync(
int id, string apiKey, out string contentType, out string charset);
}
It makes everything more meaningful.
If it's WebAPI you probably don't have access to the source for these attributes, but you can probably wrap them with a new one to add support for this feature. (Adds a little complexity, but should be really awesome to see happen.)