7
\$\begingroup\$

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?

\$\endgroup\$
  • \$\begingroup\$ P.S. I started it yesterday, so it is definitely rough ;) \$\endgroup\$ – Dmitry Nogin Sep 4 '16 at 17:24
4
\$\begingroup\$

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.)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.